aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEven Rouault <even.rouault@spatialys.com>2020-01-15 14:35:31 +0100
committerGitHub <noreply@github.com>2020-01-15 14:35:31 +0100
commit7468536f07d172592889cbb894376a0968afd4df (patch)
treea573392f71321588161dd54db732509e57f11f1f
parent9d8647371d27bdbd717644f7df5514a6f2b07a00 (diff)
parent17864e68dc7b34bb730bdc191117e1bd1d5d18ef (diff)
downloadPROJ-7468536f07d172592889cbb894376a0968afd4df.tar.gz
PROJ-7468536f07d172592889cbb894376a0968afd4df.zip
Merge pull request #1813 from rouault/rfc4_network
[RFC4_dev] Add networking capabilities
-rw-r--r--.cirrus.yml2
-rw-r--r--CMakeLists.txt32
-rw-r--r--Doxyfile2
-rw-r--r--appveyor.yml5
-rw-r--r--cmake/ProjTest.cmake19
-rw-r--r--configure.ac78
-rw-r--r--data/Makefile.am45
-rw-r--r--data/proj.ini10
-rw-r--r--data/tests/egm96_15_uncompressed_truncated.tifbin0 -> 956 bytes
-rw-r--r--data/tests/subset_of_gr3df97a.tifbin0 -> 3215 bytes
-rw-r--r--data/tests/test_hgrid.tifbin0 -> 506 bytes
-rw-r--r--data/tests/test_hgrid_degree.tifbin0 -> 680 bytes
-rw-r--r--data/tests/test_hgrid_extra_ifd_with_other_info.tifbin0 -> 1708 bytes
-rw-r--r--data/tests/test_hgrid_lon_shift_first.tifbin0 -> 764 bytes
-rw-r--r--data/tests/test_hgrid_positive_west.tifbin0 -> 764 bytes
-rw-r--r--data/tests/test_hgrid_radian.tifbin0 -> 680 bytes
-rw-r--r--data/tests/test_hgrid_separate.tifbin0 -> 514 bytes
-rw-r--r--data/tests/test_hgrid_strip.tifbin0 -> 514 bytes
-rw-r--r--data/tests/test_hgrid_tiled.tifbin0 -> 4920 bytes
-rw-r--r--data/tests/test_hgrid_tiled_separate.tifbin0 -> 2256 bytes
-rw-r--r--data/tests/test_hgrid_with_overview.tifbin0 -> 864 bytes
-rw-r--r--data/tests/test_hgrid_with_subgrid.tifbin0 -> 6943 bytes
-rw-r--r--data/tests/test_hgrid_with_subgrid_no_grid_name.tifbin0 -> 6943 bytes
-rw-r--r--data/tests/test_vgrid_bigendian.tifbin0 -> 430 bytes
-rw-r--r--data/tests/test_vgrid_bigendian_bigtiff.tifbin0 -> 568 bytes
-rw-r--r--data/tests/test_vgrid_bigtiff.tifbin0 -> 568 bytes
-rw-r--r--data/tests/test_vgrid_bottomup_with_matrix.tifbin0 -> 474 bytes
-rw-r--r--data/tests/test_vgrid_bottomup_with_scale.tifbin0 -> 430 bytes
-rw-r--r--data/tests/test_vgrid_deflate.tifbin0 -> 430 bytes
-rw-r--r--data/tests/test_vgrid_deflate_floatingpointpredictor.tifbin0 -> 422 bytes
-rw-r--r--data/tests/test_vgrid_float64.tifbin0 -> 494 bytes
-rw-r--r--data/tests/test_vgrid_in_second_channel.tifbin0 -> 632 bytes
-rw-r--r--data/tests/test_vgrid_int16.tifbin0 -> 398 bytes
-rw-r--r--data/tests/test_vgrid_int32.tifbin0 -> 430 bytes
-rw-r--r--data/tests/test_vgrid_invalid_channel_type.tifbin0 -> 560 bytes
-rw-r--r--data/tests/test_vgrid_nodata.tifbin0 -> 464 bytes
-rw-r--r--data/tests/test_vgrid_pixelisarea.tifbin0 -> 430 bytes
-rw-r--r--data/tests/test_vgrid_pixelispoint.tifbin0 -> 430 bytes
-rw-r--r--data/tests/test_vgrid_single_strip_truncated.tifbin0 -> 550 bytes
-rw-r--r--data/tests/test_vgrid_uint16.tifbin0 -> 398 bytes
-rw-r--r--data/tests/test_vgrid_uint16_with_scale_offset.tifbin0 -> 556 bytes
-rw-r--r--data/tests/test_vgrid_uint32.tifbin0 -> 430 bytes
-rw-r--r--data/tests/test_vgrid_unsupported_byte.tifbin0 -> 382 bytes
-rw-r--r--data/tests/test_vgrid_with_overview.tifbin0 -> 707 bytes
-rw-r--r--data/tests/test_vgrid_with_subgrid.tifbin0 -> 756 bytes
-rw-r--r--docs/source/apps/cs2cs.rst8
-rw-r--r--docs/source/apps/projinfo.rst10
-rw-r--r--docs/source/operations/transformations/index.rst1
-rw-r--r--docs/source/operations/transformations/xyzgridshift.rst91
-rw-r--r--docs/source/references.bib9
-rw-r--r--docs/source/resource_files.rst29
-rw-r--r--docs/source/usage/environmentvars.rst18
-rw-r--r--include/proj/coordinateoperation.hpp18
-rw-r--r--include/proj/internal/coordinateoperation_internal.hpp15
-rw-r--r--include/proj/io.hpp8
-rwxr-xr-xscripts/doxygen.sh2
-rw-r--r--scripts/reference_exported_symbols.txt16
-rwxr-xr-xscripts/reformat_cpp.sh3
-rw-r--r--src/4D_api.cpp7
-rw-r--r--src/Makefile.am9
-rw-r--r--src/apply_gridshift.cpp77
-rw-r--r--src/apps/projinfo.cpp30
-rw-r--r--src/ctx.cpp36
-rw-r--r--src/fileapi.cpp36
-rw-r--r--src/filemanager.cpp997
-rw-r--r--src/filemanager.hpp80
-rw-r--r--src/grids.cpp1964
-rw-r--r--src/grids.hpp83
-rw-r--r--src/iso19111/c_api.cpp20
-rw-r--r--src/iso19111/coordinateoperation.cpp131
-rw-r--r--src/iso19111/factory.cpp77
-rw-r--r--src/lib_proj.cmake13
-rw-r--r--src/malloc.cpp2
-rw-r--r--src/open_lib.cpp219
-rw-r--r--src/pipeline.cpp4
-rw-r--r--src/pj_list.h1
-rw-r--r--src/proj.h85
-rw-r--r--src/proj_internal.h32
-rw-r--r--src/transformations/hgridshift.cpp49
-rw-r--r--src/transformations/vgridshift.cpp75
-rw-r--r--src/transformations/xyzgridshift.cpp353
-rw-r--r--test/CMakeLists.txt1
-rw-r--r--test/cli/ntv2_out.dist3
-rwxr-xr-xtest/cli/test272
-rwxr-xr-xtest/cli/test832
-rwxr-xr-xtest/cli/testdatumfile6
-rwxr-xr-xtest/cli/testntv28
-rw-r--r--test/gie/4D-API_cs2cs-style.gie2
-rw-r--r--test/gie/Makefile.am8
-rw-r--r--test/gie/geotiff_grids.gie306
-rw-r--r--test/unit/CMakeLists.txt20
-rw-r--r--test/unit/Makefile.am10
-rw-r--r--test/unit/pj_transform_test.cpp91
-rw-r--r--test/unit/test_factory.cpp58
-rw-r--r--test/unit/test_network.cpp879
-rw-r--r--test/unit/test_operation.cpp18
-rwxr-xr-xtravis/csa/before_install.sh2
-rwxr-xr-xtravis/linux_clang/before_install.sh2
-rwxr-xr-xtravis/linux_gcc/before_install.sh1
-rwxr-xr-xtravis/linux_gcc7/before_install.sh3
-rwxr-xr-xtravis/mingw32/install.sh23
-rwxr-xr-xtravis/osx/before_install.sh7
102 files changed, 5858 insertions, 395 deletions
diff --git a/.cirrus.yml b/.cirrus.yml
index f1ff7a2e..4f140e72 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -16,7 +16,7 @@ task:
folder: $HOME/.ccache
pkginstall_script:
- - pkg install -y autoconf automake libtool pkgconf sqlite3 wget unzip ccache gmake
+ - pkg install -y autoconf automake libtool pkgconf sqlite3 tiff wget unzip ccache gmake
download_grid_script:
- wget https://download.osgeo.org/proj/proj-datumgrid-1.8.zip
- (cd data && unzip -o ../proj-datumgrid-1.8.zip)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a5715389..2125b93a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -83,6 +83,9 @@ elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Intel")
set(PROJ_CXX_WARN_FLAGS -Wall)
endif()
endif()
+
+add_definitions(-DNOMINMAX)
+
set(PROJ_C_WARN_FLAGS "${PROJ_C_WARN_FLAGS}"
CACHE STRING "C flags used to compile PROJ targets")
set(PROJ_CXX_WARN_FLAGS "${PROJ_CXX_WARN_FLAGS}"
@@ -134,6 +137,35 @@ IF("${SQLITE3_VERSION}" VERSION_LESS "3.11")
ENDIF()
################################################################################
+# Check for libtiff
+################################################################################
+
+option(DISABLE_TIFF_IS_STRONGLY_DISCOURAGED "Disable TIFF support (strongly discouraged !)" OFF)
+mark_as_advanced(DISABLE_TIFF_IS_STRONGLY_DISCOURAGED)
+if(NOT DISABLE_TIFF_IS_STRONGLY_DISCOURAGED)
+ find_package(TIFF REQUIRED)
+ if(NOT TIFF_FOUND)
+ message(SEND_ERROR "libtiff dependency not found!")
+ endif()
+ add_definitions(-DTIFF_ENABLED)
+endif()
+
+################################################################################
+# Check for curl
+################################################################################
+
+option(ENABLE_CURL "Enable Curl support" ON)
+if(ENABLE_CURL)
+ find_package(CURL REQUIRED)
+ if(CURL_FOUND)
+ boost_report_value(CURL_FOUND)
+ else()
+ message(SEND_ERROR "curl dependency not found!")
+ endif()
+ add_definitions(-DCURL_ENABLED)
+endif()
+
+################################################################################
# threading configuration
################################################################################
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
diff --git a/Doxyfile b/Doxyfile
index da9a6b61..2d263ed7 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -772,7 +772,7 @@ WARN_LOGFILE =
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.
-INPUT = src/iso19111 include/proj src/proj.h src/proj_experimental.h src/general_doc.dox
+INPUT = src/iso19111 include/proj src/proj.h src/proj_experimental.h src/general_doc.dox src/filemanager.cpp
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
diff --git a/appveyor.yml b/appveyor.yml
index a631c3e8..2511854f 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -27,9 +27,10 @@ build_script:
- set PATH=%CD%;%PATH%
- cd ..
- vcpkg install sqlite3:"%platform%"-windows
+ - vcpkg install tiff:"%platform%"-windows
+ - vcpkg install curl:"%platform%"-windows
- set SQLITE3_BIN=%APPVEYOR_BUILD_FOLDER%\sqlite3\bin
- mkdir %SQLITE3_BIN%
- - copy c:\projects\proj\vcpkg\installed\"%platform%"-windows\bin\sqlite3.dll %SQLITE3_BIN%
- ps: |
appveyor DownloadFile https://sqlite.org/2018/sqlite-tools-win32-x86-3250100.zip
7z x sqlite-tools-win32-x86-3250100.zip
@@ -47,6 +48,8 @@ build_script:
- set PROJ_DIR=%APPVEYOR_BUILD_FOLDER%\proj_dir
- cmake -G "%VS_FULL%" .. -DCMAKE_BUILD_TYPE=Release -DBUILD_LIBPROJ_SHARED="%BUILD_LIBPROJ_SHARED%" -DCMAKE_C_FLAGS="/WX" -DCMAKE_CXX_FLAGS="/WX" -DCMAKE_TOOLCHAIN_FILE=C:/projects/proj/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_INSTALL_PREFIX="%PROJ_DIR%"
- cmake --build . --config Release --target install
+ - copy c:\projects\proj\vcpkg\installed\"%platform%"-windows\bin\*.dll %PROJ_DIR%\bin
+ - dir %PROJ_DIR%\bin
test_script:
- echo test_script
diff --git a/cmake/ProjTest.cmake b/cmake/ProjTest.cmake
index ad64d0ba..6abfbcd7 100644
--- a/cmake/ProjTest.cmake
+++ b/cmake/ProjTest.cmake
@@ -26,8 +26,13 @@ function(proj_add_test_script_sh SH_NAME BIN_USE)
COMMAND ${PROJECT_SOURCE_DIR}/test/cli/${SH_NAME}
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${${BIN_USE}}
)
- set_tests_properties( ${testname}
- PROPERTIES ENVIRONMENT "PROJ_LIB=${PROJECT_BINARY_DIR}/data")
+ if(MSVC)
+ set_tests_properties( ${testname}
+ PROPERTIES ENVIRONMENT "PROJ_LIB=${PROJECT_BINARY_DIR}/data\\;${PROJECT_SOURCE_DIR}/data")
+ else()
+ set_tests_properties( ${testname}
+ PROPERTIES ENVIRONMENT "PROJ_LIB=${PROJECT_BINARY_DIR}/data:${PROJECT_SOURCE_DIR}/data")
+ endif()
endif()
endif()
@@ -43,8 +48,14 @@ function(proj_add_gie_test TESTNAME TESTCASE)
COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${GIE_BIN}
${TESTFILE}
)
- set_tests_properties( ${TESTNAME}
- PROPERTIES ENVIRONMENT "PROJ_LIB=${PROJECT_BINARY_DIR}/data")
+
+ if(MSVC)
+ set_tests_properties( ${TESTNAME}
+ PROPERTIES ENVIRONMENT "PROJ_LIB=${PROJECT_BINARY_DIR}/data\\;${PROJECT_SOURCE_DIR}/data")
+ else()
+ set_tests_properties( ${TESTNAME}
+ PROPERTIES ENVIRONMENT "PROJ_LIB=${PROJECT_BINARY_DIR}/data:${PROJECT_SOURCE_DIR}/data")
+ endif()
endfunction()
diff --git a/configure.ac b/configure.ac
index 0ea8e04a..afde2712 100644
--- a/configure.ac
+++ b/configure.ac
@@ -20,6 +20,8 @@ AC_PROG_LN_S
AC_PROG_MAKE_SET
AM_PROG_LIBTOOL
+PKG_PROG_PKG_CONFIG
+
dnl Enable as much warnings as possible
AX_CFLAGS_WARN_ALL(C_WFLAGS)
AX_CXXFLAGS_WARN_ALL(CXX_WFLAGS)
@@ -166,8 +168,8 @@ AC_SUBST(C_WFLAGS,$C_WFLAGS)
AC_SUBST(CXX_WFLAGS,$CXX_WFLAGS)
AC_SUBST(NO_ZERO_AS_NULL_POINTER_CONSTANT_FLAG,$NO_ZERO_AS_NULL_POINTER_CONSTANT_FLAG)
-CFLAGS="${CFLAGS} -fvisibility=hidden"
-CXXFLAGS="${CXXFLAGS} -fvisibility=hidden"
+CFLAGS="${CFLAGS} -fvisibility=hidden -DNOMINMAX"
+CXXFLAGS="${CXXFLAGS} -fvisibility=hidden -DNOMINMAX"
dnl Checks for libraries.
save_CFLAGS="$CFLAGS"
@@ -295,6 +297,78 @@ if test x"$SQLITE3_CHECK" != x"yes" ; then
fi
dnl ---------------------------------------------------------------------------
+dnl Check for libtiff
+dnl ---------------------------------------------------------------------------
+
+AC_ARG_ENABLE([tiff-is-strongly-discouraged],
+ AS_HELP_STRING([--disable-tiff-is-strongly-discouraged],
+ [Disable TIFF support. Strongly discouraged !]),
+ [enable_tiff=no],
+ [enable_tiff=yes])
+
+if test "x$enable_tiff" = "xyes" -o "x$enable_tiff" = ""; then
+ if test "x$TIFF_CFLAGS$TIFF_LIBS" = "x" ; then
+ if $PKG_CONFIG libtiff; then
+ PKG_CHECK_MODULES([TIFF], [libtiff])
+ else
+ PKG_CHECK_MODULES([TIFF], [libtiff-4])
+ fi
+ fi
+ TIFF_ENABLED_FLAGS=-DTIFF_ENABLED
+fi
+AC_SUBST(TIFF_CFLAGS,$TIFF_CFLAGS)
+AC_SUBST(TIFF_LIBS,$TIFF_LIBS)
+AC_SUBST(TIFF_ENABLED_FLAGS,$TIFF_ENABLED_FLAGS)
+
+dnl ---------------------------------------------------------------------------
+dnl Check for curl
+dnl ---------------------------------------------------------------------------
+
+FOUND_CURL=no
+CURL_CFLAGS=
+CURL_LIB=
+
+AC_ARG_WITH(curl,
+ [ --with-curl[=ARG] Enable curl support (ARG=path to curl-config.)],,,)
+
+dnl Clear some cache variables
+unset ac_cv_path_LIBCURL
+
+if test "`basename xx/$with_curl`" = "curl-config" ; then
+ LIBCURL_CONFIG="$with_curl"
+elif test "$with_curl" = "no" ; then
+ LIBCURL_CONFIG=no
+else
+ AC_PATH_PROG(LIBCURL_CONFIG, curl-config, not-found)
+fi
+
+if test "$LIBCURL_CONFIG" = "not-found" ; then
+ AC_MSG_ERROR([curl not found. If wanting to do a build without curl support (and thus without built-in networking capability), explictly disable it with --without-curl])
+elif test "$LIBCURL_CONFIG" != "no" ; then
+
+ CURL_VERNUM=`$LIBCURL_CONFIG --vernum`
+ CURL_VER=`$LIBCURL_CONFIG --version | awk '{print $2}'`
+
+ AC_MSG_RESULT([ found libcurl version $CURL_VER])
+
+ AC_CHECK_LIB(curl,curl_global_init,FOUND_CURL=yes,FOUND_CURL=no,`$LIBCURL_CONFIG --libs`)
+
+ if test "$FOUND_CURL" = "no" ; then
+ AC_MSG_ERROR([curl not found. If wanting to do a build without curl support (and thus without built-in networking capability), explictly disable it with --without-curl])
+ fi
+ CURL_ENABLED_FLAGS=-DCURL_ENABLED
+fi
+
+if test "$FOUND_CURL" = "yes" ; then
+ CURL_CFLAGS=`$LIBCURL_CONFIG --cflags`
+ CURL_LIBS=`$LIBCURL_CONFIG --libs`
+fi
+
+AC_SUBST(CURL_CFLAGS,$CURL_CFLAGS)
+AC_SUBST(CURL_LIBS,$CURL_LIBS)
+AC_SUBST(CURL_ENABLED_FLAGS,$CURL_ENABLED_FLAGS)
+
+dnl ---------------------------------------------------------------------------
dnl Check for external Google Test
dnl ---------------------------------------------------------------------------
diff --git a/data/Makefile.am b/data/Makefile.am
index e7c38d7c..09fa1989 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -1,6 +1,6 @@
DATAPATH = $(top_srcdir)/data
-pkgdata_DATA = GL27 nad.lst nad27 nad83 world other.extra \
+pkgdata_DATA = proj.ini GL27 nad.lst nad27 nad83 world other.extra \
CH null \
ITRF2000 ITRF2008 ITRF2014 proj.db \
projjson.schema.json
@@ -38,12 +38,51 @@ SQL_ORDERED_LIST = sql/begin.sql \
sql/customizations.sql \
sql/commit.sql
-EXTRA_DIST = GL27 nad.lst nad27 nad83 \
+EXTRA_DIST = proj.ini GL27 nad.lst nad27 nad83 \
world other.extra \
CH \
ITRF2000 ITRF2008 ITRF2014 \
projjson.schema.json \
- CMakeLists.txt tests/test_nodata.gtx null \
+ CMakeLists.txt \
+ tests/test_nodata.gtx \
+ tests/test_vgrid_bigendian_bigtiff.tif \
+ tests/test_vgrid_bigendian.tif \
+ tests/test_vgrid_bigtiff.tif \
+ tests/test_vgrid_bottomup_with_matrix.tif \
+ tests/test_vgrid_bottomup_with_scale.tif \
+ tests/test_vgrid_deflate_floatingpointpredictor.tif \
+ tests/test_vgrid_deflate.tif \
+ tests/test_vgrid_float64.tif \
+ tests/test_vgrid_in_second_channel.tif \
+ tests/test_vgrid_int16.tif \
+ tests/test_vgrid_int32.tif \
+ tests/test_vgrid_uint32.tif \
+ tests/test_vgrid_invalid_channel_type.tif \
+ tests/test_vgrid_nodata.tif \
+ tests/test_vgrid_pixelisarea.tif \
+ tests/test_vgrid_pixelispoint.tif \
+ tests/test_vgrid_uint16.tif \
+ tests/test_vgrid_uint16_with_scale_offset.tif \
+ tests/test_vgrid_unsupported_byte.tif \
+ tests/test_vgrid_with_overview.tif \
+ tests/test_vgrid_with_subgrid.tif \
+ tests/test_hgrid.tif \
+ tests/test_hgrid_separate.tif \
+ tests/test_hgrid_tiled.tif \
+ tests/test_hgrid_tiled_separate.tif \
+ tests/test_hgrid_strip.tif \
+ tests/test_hgrid_positive_west.tif \
+ tests/test_hgrid_lon_shift_first.tif \
+ tests/test_hgrid_radian.tif \
+ tests/test_hgrid_degree.tif \
+ tests/test_hgrid_with_overview.tif \
+ tests/test_hgrid_extra_ifd_with_other_info.tif \
+ tests/test_hgrid_with_subgrid.tif \
+ tests/test_hgrid_with_subgrid_no_grid_name.tif \
+ tests/subset_of_gr3df97a.tif \
+ tests/egm96_15_uncompressed_truncated.tif \
+ tests/test_vgrid_single_strip_truncated.tif \
+ null \
generate_all_sql_in.cmake sql_filelist.cmake \
$(SQL_ORDERED_LIST)
diff --git a/data/proj.ini b/data/proj.ini
new file mode 100644
index 00000000..f42bc940
--- /dev/null
+++ b/data/proj.ini
@@ -0,0 +1,10 @@
+[general]
+; Lines starting by ; are commented lines.
+;
+
+; Network capabilities disabled by default.
+; Can be overriden with the PROJ_NETWORK=ON environment variable.
+; network = on
+
+; Can be overriden with the PROJ_NETWORK_ENDPOINT environment variable.
+cdn_endpoint = https://cdn.proj.org
diff --git a/data/tests/egm96_15_uncompressed_truncated.tif b/data/tests/egm96_15_uncompressed_truncated.tif
new file mode 100644
index 00000000..bd34a7e2
--- /dev/null
+++ b/data/tests/egm96_15_uncompressed_truncated.tif
Binary files differ
diff --git a/data/tests/subset_of_gr3df97a.tif b/data/tests/subset_of_gr3df97a.tif
new file mode 100644
index 00000000..a98783f3
--- /dev/null
+++ b/data/tests/subset_of_gr3df97a.tif
Binary files differ
diff --git a/data/tests/test_hgrid.tif b/data/tests/test_hgrid.tif
new file mode 100644
index 00000000..94718c21
--- /dev/null
+++ b/data/tests/test_hgrid.tif
Binary files differ
diff --git a/data/tests/test_hgrid_degree.tif b/data/tests/test_hgrid_degree.tif
new file mode 100644
index 00000000..d06782ec
--- /dev/null
+++ b/data/tests/test_hgrid_degree.tif
Binary files differ
diff --git a/data/tests/test_hgrid_extra_ifd_with_other_info.tif b/data/tests/test_hgrid_extra_ifd_with_other_info.tif
new file mode 100644
index 00000000..b7e67af7
--- /dev/null
+++ b/data/tests/test_hgrid_extra_ifd_with_other_info.tif
Binary files differ
diff --git a/data/tests/test_hgrid_lon_shift_first.tif b/data/tests/test_hgrid_lon_shift_first.tif
new file mode 100644
index 00000000..395f0743
--- /dev/null
+++ b/data/tests/test_hgrid_lon_shift_first.tif
Binary files differ
diff --git a/data/tests/test_hgrid_positive_west.tif b/data/tests/test_hgrid_positive_west.tif
new file mode 100644
index 00000000..4ebc17cc
--- /dev/null
+++ b/data/tests/test_hgrid_positive_west.tif
Binary files differ
diff --git a/data/tests/test_hgrid_radian.tif b/data/tests/test_hgrid_radian.tif
new file mode 100644
index 00000000..30219ccd
--- /dev/null
+++ b/data/tests/test_hgrid_radian.tif
Binary files differ
diff --git a/data/tests/test_hgrid_separate.tif b/data/tests/test_hgrid_separate.tif
new file mode 100644
index 00000000..ef2ca575
--- /dev/null
+++ b/data/tests/test_hgrid_separate.tif
Binary files differ
diff --git a/data/tests/test_hgrid_strip.tif b/data/tests/test_hgrid_strip.tif
new file mode 100644
index 00000000..e38fc609
--- /dev/null
+++ b/data/tests/test_hgrid_strip.tif
Binary files differ
diff --git a/data/tests/test_hgrid_tiled.tif b/data/tests/test_hgrid_tiled.tif
new file mode 100644
index 00000000..b0d5dd8b
--- /dev/null
+++ b/data/tests/test_hgrid_tiled.tif
Binary files differ
diff --git a/data/tests/test_hgrid_tiled_separate.tif b/data/tests/test_hgrid_tiled_separate.tif
new file mode 100644
index 00000000..d7e0934f
--- /dev/null
+++ b/data/tests/test_hgrid_tiled_separate.tif
Binary files differ
diff --git a/data/tests/test_hgrid_with_overview.tif b/data/tests/test_hgrid_with_overview.tif
new file mode 100644
index 00000000..d7453b49
--- /dev/null
+++ b/data/tests/test_hgrid_with_overview.tif
Binary files differ
diff --git a/data/tests/test_hgrid_with_subgrid.tif b/data/tests/test_hgrid_with_subgrid.tif
new file mode 100644
index 00000000..46a8f2f4
--- /dev/null
+++ b/data/tests/test_hgrid_with_subgrid.tif
Binary files differ
diff --git a/data/tests/test_hgrid_with_subgrid_no_grid_name.tif b/data/tests/test_hgrid_with_subgrid_no_grid_name.tif
new file mode 100644
index 00000000..974699b5
--- /dev/null
+++ b/data/tests/test_hgrid_with_subgrid_no_grid_name.tif
Binary files differ
diff --git a/data/tests/test_vgrid_bigendian.tif b/data/tests/test_vgrid_bigendian.tif
new file mode 100644
index 00000000..5cf4a039
--- /dev/null
+++ b/data/tests/test_vgrid_bigendian.tif
Binary files differ
diff --git a/data/tests/test_vgrid_bigendian_bigtiff.tif b/data/tests/test_vgrid_bigendian_bigtiff.tif
new file mode 100644
index 00000000..a586b85f
--- /dev/null
+++ b/data/tests/test_vgrid_bigendian_bigtiff.tif
Binary files differ
diff --git a/data/tests/test_vgrid_bigtiff.tif b/data/tests/test_vgrid_bigtiff.tif
new file mode 100644
index 00000000..2a01893a
--- /dev/null
+++ b/data/tests/test_vgrid_bigtiff.tif
Binary files differ
diff --git a/data/tests/test_vgrid_bottomup_with_matrix.tif b/data/tests/test_vgrid_bottomup_with_matrix.tif
new file mode 100644
index 00000000..90f637dc
--- /dev/null
+++ b/data/tests/test_vgrid_bottomup_with_matrix.tif
Binary files differ
diff --git a/data/tests/test_vgrid_bottomup_with_scale.tif b/data/tests/test_vgrid_bottomup_with_scale.tif
new file mode 100644
index 00000000..636b7dc7
--- /dev/null
+++ b/data/tests/test_vgrid_bottomup_with_scale.tif
Binary files differ
diff --git a/data/tests/test_vgrid_deflate.tif b/data/tests/test_vgrid_deflate.tif
new file mode 100644
index 00000000..ee3b5f0a
--- /dev/null
+++ b/data/tests/test_vgrid_deflate.tif
Binary files differ
diff --git a/data/tests/test_vgrid_deflate_floatingpointpredictor.tif b/data/tests/test_vgrid_deflate_floatingpointpredictor.tif
new file mode 100644
index 00000000..5fd7b9fa
--- /dev/null
+++ b/data/tests/test_vgrid_deflate_floatingpointpredictor.tif
Binary files differ
diff --git a/data/tests/test_vgrid_float64.tif b/data/tests/test_vgrid_float64.tif
new file mode 100644
index 00000000..16b3e790
--- /dev/null
+++ b/data/tests/test_vgrid_float64.tif
Binary files differ
diff --git a/data/tests/test_vgrid_in_second_channel.tif b/data/tests/test_vgrid_in_second_channel.tif
new file mode 100644
index 00000000..d377f8b7
--- /dev/null
+++ b/data/tests/test_vgrid_in_second_channel.tif
Binary files differ
diff --git a/data/tests/test_vgrid_int16.tif b/data/tests/test_vgrid_int16.tif
new file mode 100644
index 00000000..1c69b5d6
--- /dev/null
+++ b/data/tests/test_vgrid_int16.tif
Binary files differ
diff --git a/data/tests/test_vgrid_int32.tif b/data/tests/test_vgrid_int32.tif
new file mode 100644
index 00000000..1b6dfd7b
--- /dev/null
+++ b/data/tests/test_vgrid_int32.tif
Binary files differ
diff --git a/data/tests/test_vgrid_invalid_channel_type.tif b/data/tests/test_vgrid_invalid_channel_type.tif
new file mode 100644
index 00000000..ec9e641f
--- /dev/null
+++ b/data/tests/test_vgrid_invalid_channel_type.tif
Binary files differ
diff --git a/data/tests/test_vgrid_nodata.tif b/data/tests/test_vgrid_nodata.tif
new file mode 100644
index 00000000..65ec5343
--- /dev/null
+++ b/data/tests/test_vgrid_nodata.tif
Binary files differ
diff --git a/data/tests/test_vgrid_pixelisarea.tif b/data/tests/test_vgrid_pixelisarea.tif
new file mode 100644
index 00000000..a5409f66
--- /dev/null
+++ b/data/tests/test_vgrid_pixelisarea.tif
Binary files differ
diff --git a/data/tests/test_vgrid_pixelispoint.tif b/data/tests/test_vgrid_pixelispoint.tif
new file mode 100644
index 00000000..cfeb598f
--- /dev/null
+++ b/data/tests/test_vgrid_pixelispoint.tif
Binary files differ
diff --git a/data/tests/test_vgrid_single_strip_truncated.tif b/data/tests/test_vgrid_single_strip_truncated.tif
new file mode 100644
index 00000000..9a0030f6
--- /dev/null
+++ b/data/tests/test_vgrid_single_strip_truncated.tif
Binary files differ
diff --git a/data/tests/test_vgrid_uint16.tif b/data/tests/test_vgrid_uint16.tif
new file mode 100644
index 00000000..a03d9a73
--- /dev/null
+++ b/data/tests/test_vgrid_uint16.tif
Binary files differ
diff --git a/data/tests/test_vgrid_uint16_with_scale_offset.tif b/data/tests/test_vgrid_uint16_with_scale_offset.tif
new file mode 100644
index 00000000..b08fa4a3
--- /dev/null
+++ b/data/tests/test_vgrid_uint16_with_scale_offset.tif
Binary files differ
diff --git a/data/tests/test_vgrid_uint32.tif b/data/tests/test_vgrid_uint32.tif
new file mode 100644
index 00000000..cae7e9e7
--- /dev/null
+++ b/data/tests/test_vgrid_uint32.tif
Binary files differ
diff --git a/data/tests/test_vgrid_unsupported_byte.tif b/data/tests/test_vgrid_unsupported_byte.tif
new file mode 100644
index 00000000..ccf03fc8
--- /dev/null
+++ b/data/tests/test_vgrid_unsupported_byte.tif
Binary files differ
diff --git a/data/tests/test_vgrid_with_overview.tif b/data/tests/test_vgrid_with_overview.tif
new file mode 100644
index 00000000..aa15aa1d
--- /dev/null
+++ b/data/tests/test_vgrid_with_overview.tif
Binary files differ
diff --git a/data/tests/test_vgrid_with_subgrid.tif b/data/tests/test_vgrid_with_subgrid.tif
new file mode 100644
index 00000000..5c7584c4
--- /dev/null
+++ b/data/tests/test_vgrid_with_subgrid.tif
Binary files differ
diff --git a/docs/source/apps/cs2cs.rst b/docs/source/apps/cs2cs.rst
index d7f0e9ad..a4069472 100644
--- a/docs/source/apps/cs2cs.rst
+++ b/docs/source/apps/cs2cs.rst
@@ -175,6 +175,14 @@ normally be in DMS format (use ``-f %.12f`` for decimal degrees with 12 decimal
places), while projected (cartesian) coordinates will be in linear
(meter, feet) units.
+Use of remote grids
+-------------------
+
+.. versionadded:: 7.0.0
+
+If the :envvar:`PROJ_NETWORK` environment variable is set to ``ON``,
+:program:`cs2cs` will attempt to use remote grids stored on CDN (Content
+Delivery Network) storage, when they are not available locally.
Examples
********
diff --git a/docs/source/apps/projinfo.rst b/docs/source/apps/projinfo.rst
index 6236056d..a9b6aecc 100644
--- a/docs/source/apps/projinfo.rst
+++ b/docs/source/apps/projinfo.rst
@@ -20,7 +20,7 @@ Synopsis
| [[--area name_or_code] | [--bbox west_long,south_lat,east_long,north_lat]]
| [--spatial-test contains|intersects]
| [--crs-extent-use none|both|intersection|smallest]
- | [--grid-check none|discard_missing|sort] [--show-superseded]
+ | [--grid-check none|discard_missing|sort|known_available] [--show-superseded]
| [--pivot-crs always|if_no_direct_transformation|never|{auth:code[,auth:code]*}]
| [--boundcrs-to-wgs84]
| [--main-db-path path] [--aux-db-path path]*
@@ -148,12 +148,13 @@ The following control parameters can appear in any order:
.. note:: only used for coordinate operation computation
-.. option:: --grid-check none|discard_missing|sort
+.. option:: --grid-check none|discard_missing|sort|known_available
Specify how the presence or absence of a horizontal or vertical shift grid
required for a coordinate operation affects the results returned when
researching coordinate operations between 2 CRS.
- The default strategy is ``sort``: in that case, all candidate
+ The default strategy is ``sort`` (if :envvar:`PROJ_NETWORK` is not defined).
+ In that case, all candidate
operations are returned, but the actual availability of the grids is used
to determine the sorting order. That is, if a coordinate operation involves
using a grid that is not available in the PROJ resource directories
@@ -163,6 +164,9 @@ The following control parameters can appear in any order:
this returns the results as if all the grids where available.
The ``discard_missing`` strategy discards results that involve grids not
present in the PROJ resource directories.
+ The ``known_available`` strategy discards results that involve grids not
+ present in the PROJ resource directories and that are not known of the CDN.
+ This is the default strategy is :envvar:`PROJ_NETWORK` is set to ``ON``.
.. note:: only used for coordinate operation computation
diff --git a/docs/source/operations/transformations/index.rst b/docs/source/operations/transformations/index.rst
index d4bbf4e0..6bd503d4 100644
--- a/docs/source/operations/transformations/index.rst
+++ b/docs/source/operations/transformations/index.rst
@@ -19,3 +19,4 @@ systems are based on different datums.
molobadekas
hgridshift
vgridshift
+ xyzgridshift
diff --git a/docs/source/operations/transformations/xyzgridshift.rst b/docs/source/operations/transformations/xyzgridshift.rst
new file mode 100644
index 00000000..c49b7592
--- /dev/null
+++ b/docs/source/operations/transformations/xyzgridshift.rst
@@ -0,0 +1,91 @@
+.. _xyzgridshift:
+
+================================================================================
+Geocentric grid shift
+================================================================================
+
+.. versionadded:: 7.0.0
+
+Geocentric translation using a grid shift
+
++-----------------+-------------------------------------------------------------------+
+| **Domain** | 3D |
++-----------------+-------------------------------------------------------------------+
+| **Input type** | Cartesian coordinates |
++-----------------+-------------------------------------------------------------------+
+| **Output type** | Cartesian coordinates |
++-----------------+-------------------------------------------------------------------+
+
+Perform a geocentric translation by bilinear interpolation of dx, dy, dz
+translation values from a grid. The grid is referenced against either the
+2D geographic CRS corresponding to the input (or sometimes output) CRS.
+
+This method is described (in French) in :cite:`NTF_88`
+and as EPSG operation methode code 9655 in :cite:`IOGP2018` (§2.4.4.1.1
+France geocentric interpolation).
+
+The translation in the grids are added to the input coordinates in the forward direction,
+and subtracted in the reverse direction.
+By default (if grid_ref=input_crs), in the forward direction, the input coordinates
+are converted to their geographic equivalent to directly read and interpolate from
+the grid. In the reverse direction, an iterative method is used to be able to find
+the grid locations to read.
+If grid_ref=output_crs is used, then the reverse strategy is applied: iterative
+method in the forward direction, and direct read in the reverse direction.
+
+Example
+-------------------------------------------------------------------------------
+
+NTF to RGF93 transformation using gr3df97a.tif grid
+
+::
+
+ +proj=pipeline
+ +step +proj=push +v_3
+ +step +proj=cart +ellps=clrk80ign
+ +step +proj=xyzgridshift +grids=gr3df97a.tif +grid_ref=output_crs +ellps=GRS80
+ +step +proj=cart +ellps=GRS80 +inv
+ +step +proj=pop +v_3
+
+Parameters
+################################################################################
+
+The ellipsoid parameters should be the ones consistent with ``grid_ref``.
+They are used to perform a geocentric to geographic conversion to find the
+translation parameters.
+
+
+Required
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+.. include:: ../options/ellps.rst
+
+.. option:: +grids=<list>
+
+ Comma-separated list of grids to load. If a grid is prefixed by an `@` the
+ grid is considered optional and PROJ will the not complain if the grid is
+ not available.
+
+ Grids are expected to be in GeoTIFF format. If no metadata is provided,
+ the first, second and third samples are assumed to be the geocentric
+ translation along X, Y and Z axis respectively, in metres.
+
+Optional
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+.. option:: +grid_ref=input_crs/output_crs
+
+ Specify in which CRS the grid is referenced to. The default value is
+ input_crs. That is the grid is referenced in the geographic CRS corresponding
+ to the input geocentric CRS.
+
+ If output_crs is specified, the grid is referenced in the geographic CRS corresponding
+ to the output geocentric CRS. This is for example the case for the French
+ gr3df97a.tif grid converting from NTF to RGF93, but referenced against RGF93.
+ Thus in the forward direction (NTF->RGF93), an iterative method is used to find
+ the appropriate shift.
+
+.. option:: +multiplier=<value>
+
+ Specify the multiplier to apply to the grid values. Defaults to 1.0
+
diff --git a/docs/source/references.bib b/docs/source/references.bib
index 0668e10d..8e527ea1 100644
--- a/docs/source/references.bib
+++ b/docs/source/references.bib
@@ -230,6 +230,15 @@
Doi = {10.2312/PE/PG/PG2012short/005-010}
}
+@TechReport{NTF_88,
+ Title = {Grille de parametres de transformation de coordonnees - GR3DF97A - Notice d'utilisation},
+ Author = {IGN},
+ Institution = {Service de Geodesie et Nivellement, Institut Geographique National},
+ Year = {1997},
+
+ Url = {https://geodesie.ign.fr/contenu/fichiers/documentation/algorithmes/notice/NTG_88.pdf}
+}
+
@TechReport{ONeilLaubscher1976,
Title = {Extended Studies of a Quadrilateralized Spherical Cube Earth Data Base},
Author = {E. M. O'Neill and R. E. Laubscher},
diff --git a/docs/source/resource_files.rst b/docs/source/resource_files.rst
index 13b9386d..ea02fd4b 100644
--- a/docs/source/resource_files.rst
+++ b/docs/source/resource_files.rst
@@ -40,6 +40,11 @@ The following paths are checked in order:
PROJ installation is not moved somewhere else.
- The current directory
+When networking capabilities are enabled, either by API with the
+:c:func:`proj_context_set_enable_network` function or when the
+:envvar:`PROJ_NETWORK` environment variable is set to ``ON``, PROJ will
+attempt to use remote grids stored on CDN (Content Delivery Network) storage.
+
.. _proj-db:
proj.db
@@ -49,6 +54,30 @@ A proj installation includes a SQLite database of transformation information
that must be accessible for the library to work properly. The library will
print an error if the database can't be found.
+proj.ini
+-------------------------------------------------------------------------------
+
+.. versionadded:: 7.0
+
+proj.ini is a text configuration file, mostly dedicated at setting up network
+related parameters.
+
+Its default content is:
+
+::
+
+ [general]
+ ; Lines starting by ; are commented lines.
+ ;
+
+ ; Network capabilities disabled by default.
+ ; Can be overriden with the PROJ_NETWORK=ON environment variable.
+ ; network = on
+
+ ; Can be overriden with the PROJ_NETWORK_ENDPOINT environment variable.
+ cdn_endpoint = https://cdn.proj.org
+
+
Transformation grids
-------------------------------------------------------------------------------
diff --git a/docs/source/usage/environmentvars.rst b/docs/source/usage/environmentvars.rst
index 457432a0..24ae4a45 100644
--- a/docs/source/usage/environmentvars.rst
+++ b/docs/source/usage/environmentvars.rst
@@ -56,3 +56,21 @@ done by setting the variable with no content::
Set the debug level of PROJ. The default debug level is zero, which results
in no debug output when using PROJ. A number from 1-3, whit 3 being the most
verbose setting.
+
+.. envvar:: PROJ_NETWORK
+
+ .. versionadded:: 7.0.0
+
+ If set to ON, enable the capability to use remote grids stored on CDN
+ (Content Delivery Network) storage, when grids are not available locally.
+ Alternatively, the :c:func:`proj_context_set_enable_network` function can
+ be used.
+
+.. envvar:: PROJ_NETWORK_ENDPOINT
+
+ .. versionadded:: 7.0.0
+
+ Define the endpoint of the CDN storage. Normally defined through the proj.ini
+ configuration file locale in PROJ_LIB.
+ Alternatively, the :c:func:`proj_context_set_url_endpoint` function can
+ be used.
diff --git a/include/proj/coordinateoperation.hpp b/include/proj/coordinateoperation.hpp
index 55bc28a0..bee3eff2 100644
--- a/include/proj/coordinateoperation.hpp
+++ b/include/proj/coordinateoperation.hpp
@@ -144,10 +144,12 @@ class PROJ_GCC_DLL CoordinateOperation : public common::ObjectUsage,
/** \brief Return grids needed by an operation. */
PROJ_DLL virtual std::set<GridDescription>
- gridsNeeded(const io::DatabaseContextPtr &databaseContext) const = 0;
+ gridsNeeded(const io::DatabaseContextPtr &databaseContext,
+ bool considerKnownGridsAsAvailable) const = 0;
PROJ_DLL bool
- isPROJInstantiable(const io::DatabaseContextPtr &databaseContext) const;
+ isPROJInstantiable(const io::DatabaseContextPtr &databaseContext,
+ bool considerKnownGridsAsAvailable) const;
PROJ_DLL bool hasBallparkTransformation() const;
@@ -595,7 +597,8 @@ class PROJ_GCC_DLL SingleOperation : virtual public CoordinateOperation {
std::vector<metadata::PositionalAccuracyNNPtr>());
PROJ_DLL std::set<GridDescription>
- gridsNeeded(const io::DatabaseContextPtr &databaseContext) const override;
+ gridsNeeded(const io::DatabaseContextPtr &databaseContext,
+ bool considerKnownGridsAsAvailable) const override;
PROJ_DLL std::list<std::string> validateParameters() const;
@@ -1672,7 +1675,8 @@ class PROJ_GCC_DLL ConcatenatedOperation final : public CoordinateOperation {
bool checkExtent); // throw InvalidOperation
PROJ_DLL std::set<GridDescription>
- gridsNeeded(const io::DatabaseContextPtr &databaseContext) const override;
+ gridsNeeded(const io::DatabaseContextPtr &databaseContext,
+ bool considerKnownGridsAsAvailable) const override;
PROJ_PRIVATE :
@@ -1801,6 +1805,12 @@ class PROJ_GCC_DLL CoordinateOperationContext {
/** Ignore grid availability at all. Results will be presented as if
* all grids were available. */
IGNORE_GRID_AVAILABILITY,
+
+ /** Results will be presented as if grids known to PROJ (that is
+ * registered in the grid_alternatives table of its database) were
+ * available. Used typically when networking is enabled.
+ */
+ KNOWN_AVAILABLE,
};
PROJ_DLL void setGridAvailabilityUse(GridAvailabilityUse use);
diff --git a/include/proj/internal/coordinateoperation_internal.hpp b/include/proj/internal/coordinateoperation_internal.hpp
index 7ae2cd78..921021ec 100644
--- a/include/proj/internal/coordinateoperation_internal.hpp
+++ b/include/proj/internal/coordinateoperation_internal.hpp
@@ -177,8 +177,10 @@ class InverseConversion : public Conversion, public InverseCoordinateOperation {
// 'osgeo::proj::operation::SingleOperation::osgeo::proj::operation::SingleOperation::gridsNeeded'
// via dominance
std::set<GridDescription>
- gridsNeeded(const io::DatabaseContextPtr &databaseContext) const override {
- return SingleOperation::gridsNeeded(databaseContext);
+ gridsNeeded(const io::DatabaseContextPtr &databaseContext,
+ bool considerKnownGridsAsAvailable) const override {
+ return SingleOperation::gridsNeeded(databaseContext,
+ considerKnownGridsAsAvailable);
}
#endif
@@ -227,8 +229,10 @@ class InverseTransformation : public Transformation,
// 'osgeo::proj::operation::SingleOperation::osgeo::proj::operation::SingleOperation::gridsNeeded'
// via dominance
std::set<GridDescription>
- gridsNeeded(const io::DatabaseContextPtr &databaseContext) const override {
- return SingleOperation::gridsNeeded(databaseContext);
+ gridsNeeded(const io::DatabaseContextPtr &databaseContext,
+ bool considerKnownGridsAsAvailable) const override {
+ return SingleOperation::gridsNeeded(databaseContext,
+ considerKnownGridsAsAvailable);
}
#endif
@@ -269,7 +273,8 @@ class PROJBasedOperation : public SingleOperation {
bool hasRoughTransformation);
std::set<GridDescription>
- gridsNeeded(const io::DatabaseContextPtr &databaseContext) const override;
+ gridsNeeded(const io::DatabaseContextPtr &databaseContext,
+ bool considerKnownGridsAsAvailable) const override;
protected:
PROJBasedOperation(const PROJBasedOperation &) = default;
diff --git a/include/proj/io.hpp b/include/proj/io.hpp
index be293eb9..2839896e 100644
--- a/include/proj/io.hpp
+++ b/include/proj/io.hpp
@@ -832,6 +832,7 @@ class PROJ_GCC_DLL DatabaseContext {
bool &inverse) const;
PROJ_DLL bool lookForGridInfo(const std::string &projFilename,
+ bool considerKnownGridsAsAvailable,
std::string &fullFilename,
std::string &packageName, std::string &url,
bool &directDownload, bool &openLicense,
@@ -1050,7 +1051,8 @@ class PROJ_GCC_DLL AuthorityFactory {
const std::string &sourceCRSAuthName, const std::string &sourceCRSCode,
const std::string &targetCRSAuthName, const std::string &targetCRSCode,
bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
- bool discardSuperseded, bool tryReverseOrder = false,
+ bool considerKnownGridsAsAvailable, bool discardSuperseded,
+ bool tryReverseOrder = false,
bool reportOnlyIntersectingTransformations = false,
const metadata::ExtentPtr &intersectingExtent1 = nullptr,
const metadata::ExtentPtr &intersectingExtent2 = nullptr) const;
@@ -1060,7 +1062,7 @@ class PROJ_GCC_DLL AuthorityFactory {
const std::string &sourceCRSAuthName, const std::string &sourceCRSCode,
const std::string &targetCRSAuthName, const std::string &targetCRSCode,
bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
- bool discardSuperseded,
+ bool considerKnownGridsAsAvailable, bool discardSuperseded,
const std::vector<std::pair<std::string, std::string>>
&intermediateCRSAuthCodes,
ObjectType allowedIntermediateObjectType = ObjectType::CRS,
@@ -1126,7 +1128,7 @@ class PROJ_GCC_DLL AuthorityFactory {
const std::string &sourceCRSCode, const crs::CRSNNPtr &targetCRS,
const std::string &targetCRSAuthName, const std::string &targetCRSCode,
bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
- bool discardSuperseded,
+ bool considerKnownGridsAsAvailable, bool discardSuperseded,
const std::vector<std::string> &allowedAuthorities,
const metadata::ExtentPtr &intersectingExtent1,
const metadata::ExtentPtr &intersectingExtent2) const;
diff --git a/scripts/doxygen.sh b/scripts/doxygen.sh
index 48646237..9646eed8 100755
--- a/scripts/doxygen.sh
+++ b/scripts/doxygen.sh
@@ -34,7 +34,7 @@ fi
mkdir -p docs/build/tmp_breathe
python scripts/generate_breathe_friendly_general_doc.py
rm -rf docs/build/xml/
-(cat Doxyfile; printf "GENERATE_HTML=NO\nGENERATE_XML=YES\nINPUT= src/iso19111 include/proj src/proj.h docs/build/tmp_breathe/general_doc.dox.reworked.h") | doxygen - > docs/build/tmp_breathe/docs_log.txt 2>&1
+(cat Doxyfile; printf "GENERATE_HTML=NO\nGENERATE_XML=YES\nINPUT= src/iso19111 include/proj src/proj.h src/filemanager.cpp docs/build/tmp_breathe/general_doc.dox.reworked.h") | doxygen - > docs/build/tmp_breathe/docs_log.txt 2>&1
if grep -i warning docs/build/tmp_breathe/docs_log.txt; then
echo "Doxygen warnings found" && cat docs/build/tmp_breathe/docs_log.txt && /bin/false;
else
diff --git a/scripts/reference_exported_symbols.txt b/scripts/reference_exported_symbols.txt
index e20f29b3..3e17a3c7 100644
--- a/scripts/reference_exported_symbols.txt
+++ b/scripts/reference_exported_symbols.txt
@@ -299,8 +299,8 @@ osgeo::proj::io::AuthorityFactory::create(dropbox::oxygen::nn<std::shared_ptr<os
osgeo::proj::io::AuthorityFactory::createEllipsoid(std::string const&) const
osgeo::proj::io::AuthorityFactory::createExtent(std::string const&) const
osgeo::proj::io::AuthorityFactory::createFromCoordinateReferenceSystemCodes(std::string const&, std::string const&) const
-osgeo::proj::io::AuthorityFactory::createFromCoordinateReferenceSystemCodes(std::string const&, std::string const&, std::string const&, std::string const&, bool, bool, bool, bool, bool, std::shared_ptr<osgeo::proj::metadata::Extent> const&, std::shared_ptr<osgeo::proj::metadata::Extent> const&) const
-osgeo::proj::io::AuthorityFactory::createFromCRSCodesWithIntermediates(std::string const&, std::string const&, std::string const&, std::string const&, bool, bool, bool, std::vector<std::pair<std::string, std::string>, std::allocator<std::pair<std::string, std::string> > > const&, osgeo::proj::io::AuthorityFactory::ObjectType, std::vector<std::string, std::allocator<std::string> > const&, std::shared_ptr<osgeo::proj::metadata::Extent> const&, std::shared_ptr<osgeo::proj::metadata::Extent> const&) const
+osgeo::proj::io::AuthorityFactory::createFromCoordinateReferenceSystemCodes(std::string const&, std::string const&, std::string const&, std::string const&, bool, bool, bool, bool, bool, bool, std::shared_ptr<osgeo::proj::metadata::Extent> const&, std::shared_ptr<osgeo::proj::metadata::Extent> const&) const
+osgeo::proj::io::AuthorityFactory::createFromCRSCodesWithIntermediates(std::string const&, std::string const&, std::string const&, std::string const&, bool, bool, bool, bool, std::vector<std::pair<std::string, std::string>, std::allocator<std::pair<std::string, std::string> > > const&, osgeo::proj::io::AuthorityFactory::ObjectType, std::vector<std::string, std::allocator<std::string> > const&, std::shared_ptr<osgeo::proj::metadata::Extent> const&, std::shared_ptr<osgeo::proj::metadata::Extent> const&) const
osgeo::proj::io::AuthorityFactory::createGeodeticCRS(std::string const&) const
osgeo::proj::io::AuthorityFactory::createGeodeticDatum(std::string const&) const
osgeo::proj::io::AuthorityFactory::createGeographicCRS(std::string const&) const
@@ -330,7 +330,7 @@ osgeo::proj::io::DatabaseContext::getDatabaseStructure() const
osgeo::proj::io::DatabaseContext::getMetadata(char const*) const
osgeo::proj::io::DatabaseContext::getPath() const
osgeo::proj::io::DatabaseContext::getSqliteHandle() const
-osgeo::proj::io::DatabaseContext::lookForGridInfo(std::string const&, std::string&, std::string&, std::string&, bool&, bool&, bool&) const
+osgeo::proj::io::DatabaseContext::lookForGridInfo(std::string const&, bool, std::string&, std::string&, std::string&, bool&, bool&, bool&) const
osgeo::proj::io::FactoryException::~FactoryException()
osgeo::proj::io::FactoryException::FactoryException(char const*)
osgeo::proj::io::FactoryException::FactoryException(osgeo::proj::io::FactoryException const&)
@@ -465,7 +465,7 @@ osgeo::proj::metadata::VerticalExtent::~VerticalExtent()
osgeo::proj::operation::ConcatenatedOperation::~ConcatenatedOperation()
osgeo::proj::operation::ConcatenatedOperation::createComputeMetadata(std::vector<dropbox::oxygen::nn<std::shared_ptr<osgeo::proj::operation::CoordinateOperation> >, std::allocator<dropbox::oxygen::nn<std::shared_ptr<osgeo::proj::operation::CoordinateOperation> > > > const&, bool)
osgeo::proj::operation::ConcatenatedOperation::create(osgeo::proj::util::PropertyMap const&, std::vector<dropbox::oxygen::nn<std::shared_ptr<osgeo::proj::operation::CoordinateOperation> >, std::allocator<dropbox::oxygen::nn<std::shared_ptr<osgeo::proj::operation::CoordinateOperation> > > > const&, std::vector<dropbox::oxygen::nn<std::shared_ptr<osgeo::proj::metadata::PositionalAccuracy> >, std::allocator<dropbox::oxygen::nn<std::shared_ptr<osgeo::proj::metadata::PositionalAccuracy> > > > const&)
-osgeo::proj::operation::ConcatenatedOperation::gridsNeeded(std::shared_ptr<osgeo::proj::io::DatabaseContext> const&) const
+osgeo::proj::operation::ConcatenatedOperation::gridsNeeded(std::shared_ptr<osgeo::proj::io::DatabaseContext> const&, bool) const
osgeo::proj::operation::ConcatenatedOperation::inverse() const
osgeo::proj::operation::ConcatenatedOperation::operations() const
osgeo::proj::operation::Conversion::~Conversion()
@@ -575,7 +575,7 @@ osgeo::proj::operation::CoordinateOperationFactory::createOperation(dropbox::oxy
osgeo::proj::operation::CoordinateOperationFactory::createOperations(dropbox::oxygen::nn<std::shared_ptr<osgeo::proj::crs::CRS> > const&, dropbox::oxygen::nn<std::shared_ptr<osgeo::proj::crs::CRS> > const&, dropbox::oxygen::nn<std::unique_ptr<osgeo::proj::operation::CoordinateOperationContext, std::default_delete<osgeo::proj::operation::CoordinateOperationContext> > > const&) const
osgeo::proj::operation::CoordinateOperation::hasBallparkTransformation() const
osgeo::proj::operation::CoordinateOperation::interpolationCRS() const
-osgeo::proj::operation::CoordinateOperation::isPROJInstantiable(std::shared_ptr<osgeo::proj::io::DatabaseContext> const&) const
+osgeo::proj::operation::CoordinateOperation::isPROJInstantiable(std::shared_ptr<osgeo::proj::io::DatabaseContext> const&, bool) const
osgeo::proj::operation::CoordinateOperation::normalizeForVisualization() const
osgeo::proj::operation::CoordinateOperation::operationVersion() const
osgeo::proj::operation::CoordinateOperation::shallowClone() const
@@ -621,7 +621,7 @@ osgeo::proj::operation::ParameterValue::value() const
osgeo::proj::operation::ParameterValue::valueFile() const
osgeo::proj::operation::PointMotionOperation::~PointMotionOperation()
osgeo::proj::operation::SingleOperation::createPROJBased(osgeo::proj::util::PropertyMap const&, std::string const&, std::shared_ptr<osgeo::proj::crs::CRS> const&, std::shared_ptr<osgeo::proj::crs::CRS> const&, std::vector<dropbox::oxygen::nn<std::shared_ptr<osgeo::proj::metadata::PositionalAccuracy> >, std::allocator<dropbox::oxygen::nn<std::shared_ptr<osgeo::proj::metadata::PositionalAccuracy> > > > const&)
-osgeo::proj::operation::SingleOperation::gridsNeeded(std::shared_ptr<osgeo::proj::io::DatabaseContext> const&) const
+osgeo::proj::operation::SingleOperation::gridsNeeded(std::shared_ptr<osgeo::proj::io::DatabaseContext> const&, bool) const
osgeo::proj::operation::SingleOperation::method() const
osgeo::proj::operation::SingleOperation::parameterValue(int) const
osgeo::proj::operation::SingleOperation::parameterValueMeasure(int) const
@@ -707,6 +707,7 @@ pj_chomp(char*)
pj_cleanup_lock
pj_clear_initcache
pj_compare_datums
+pj_context_is_network_enabled(projCtx_t*)
pj_ctx_alloc
pj_ctx_fclose
pj_ctx_fgets
@@ -794,9 +795,12 @@ proj_context_get_use_proj4_init_rules
proj_context_guess_wkt_dialect
proj_context_set_autoclose_database
proj_context_set_database_path
+proj_context_set_enable_network
proj_context_set_file_finder
+proj_context_set_network_callbacks
proj_context_set(PJconsts*, projCtx_t*)
proj_context_set_search_paths
+proj_context_set_url_endpoint
proj_context_use_proj4_init_rules
proj_convert_conversion_to_other_method
proj_coord
diff --git a/scripts/reformat_cpp.sh b/scripts/reformat_cpp.sh
index 899e665f..ca20e0d8 100755
--- a/scripts/reformat_cpp.sh
+++ b/scripts/reformat_cpp.sh
@@ -17,7 +17,8 @@ TOPDIR="$SCRIPT_DIR/.."
for i in "$TOPDIR"/include/proj/*.hpp "$TOPDIR"/include/proj/internal/*.hpp \
"$TOPDIR"/src/iso19111/*.cpp "$TOPDIR"/test/unit/*.cpp "$TOPDIR"/src/apps/projinfo.cpp \
- "$TOPDIR"/src/tracing.cpp "$TOPDIR"/src/grids.hpp "$TOPDIR"/src/grids.cpp; do
+ "$TOPDIR"/src/tracing.cpp "$TOPDIR"/src/grids.hpp "$TOPDIR"/src/grids.cpp \
+ "$TOPDIR"/src/filemanager.hpp "$TOPDIR"/src/filemanager.cpp ; do
if ! echo "$i" | grep -q "lru_cache.hpp"; then
"$SCRIPT_DIR"/reformat.sh "$i";
fi
diff --git a/src/4D_api.cpp b/src/4D_api.cpp
index efb4a86a..cee8262e 100644
--- a/src/4D_api.cpp
+++ b/src/4D_api.cpp
@@ -262,7 +262,7 @@ similarly, but prefers the 2D resp. 3D interfaces if available.
auto coordOperation = dynamic_cast<
NS_PROJ::operation::CoordinateOperation*>(alt.pj->iso_obj.get());
if( coordOperation ) {
- if( coordOperation->gridsNeeded(dbContext).empty() ) {
+ if( coordOperation->gridsNeeded(dbContext, true).empty() ) {
if( P->iCurCoordOp != i ) {
std::string msg("Using coordinate operation ");
msg += alt.name;
@@ -1117,7 +1117,10 @@ PJ *proj_create_crs_to_crs_from_pj (PJ_CONTEXT *ctx, const PJ *source_crs, cons
proj_operation_factory_context_set_spatial_criterion(
ctx, operation_ctx, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION);
proj_operation_factory_context_set_grid_availability_use(
- ctx, operation_ctx, PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID);
+ ctx, operation_ctx,
+ pj_context_is_network_enabled(ctx) ?
+ PROJ_GRID_AVAILABILITY_KNOWN_AVAILABLE:
+ PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID);
auto op_list = proj_create_operations(ctx, source_crs, target_crs, operation_ctx);
diff --git a/src/Makefile.am b/src/Makefile.am
index a12de4e1..afe4bcb7 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -7,7 +7,7 @@ TESTS = geodtest
check_PROGRAMS = geodtest
AM_CPPFLAGS = -DPROJ_LIB=\"$(pkgdatadir)\" \
- -DMUTEX_@MUTEX_SETTING@ @JNI_INCLUDE@ -I$(top_srcdir)/include @SQLITE3_CFLAGS@
+ -DMUTEX_@MUTEX_SETTING@ @JNI_INCLUDE@ -I$(top_srcdir)/include @SQLITE3_CFLAGS@ @TIFF_CFLAGS@ @TIFF_ENABLED_FLAGS@ @CURL_CFLAGS@ @CURL_ENABLED_FLAGS@
AM_CXXFLAGS = @CXX_WFLAGS@ @FLTO_FLAG@
include_HEADERS = proj.h proj_experimental.h proj_constants.h proj_api.h geodesic.h \
@@ -43,7 +43,7 @@ geodtest_LDADD = libproj.la
lib_LTLIBRARIES = libproj.la
libproj_la_LDFLAGS = -no-undefined -version-info 17:0:2
-libproj_la_LIBADD = @SQLITE3_LIBS@
+libproj_la_LIBADD = @SQLITE3_LIBS@ @TIFF_LIBS@ @CURL_LIBS@
libproj_la_SOURCES = \
pj_list.h proj_internal.h \
@@ -183,6 +183,7 @@ libproj_la_SOURCES = \
transformations/horner.cpp \
transformations/molodensky.cpp \
transformations/vgridshift.cpp \
+ transformations/xyzgridshift.cpp \
\
aasincos.cpp adjlon.cpp \
dmstor.cpp auth.cpp \
@@ -214,7 +215,9 @@ libproj_la_SOURCES = \
tracing.cpp \
\
grids.hpp \
- grids.cpp
+ grids.cpp \
+ filemanager.hpp \
+ filemanager.cpp
# The sed hack is to please MSVC
diff --git a/src/apply_gridshift.cpp b/src/apply_gridshift.cpp
index a0ffa394..4ef86fc0 100644
--- a/src/apply_gridshift.cpp
+++ b/src/apply_gridshift.cpp
@@ -36,6 +36,8 @@
#include <stdio.h>
#include <string.h>
+#include <limits>
+
#include "proj.h"
#include "proj_internal.h"
#include "proj/internal/internal.hpp"
@@ -117,7 +119,7 @@ ListOfHGrids proj_hgrid_init(PJ* P, const char *gridkey) {
typedef struct { pj_int32 lam, phi; } ILP;
-static PJ_LP nad_intr(PJ_LP t, const HorizontalShiftGrid* grid) {
+static PJ_LP nad_intr(PJ_LP t, const HorizontalShiftGrid* grid, bool compensateNTConvention) {
PJ_LP val, frct;
ILP indx;
int in;
@@ -162,10 +164,10 @@ static PJ_LP nad_intr(PJ_LP t, const HorizontalShiftGrid* grid) {
float f10Lon = 0, f10Lat = 0;
float f01Lon = 0, f01Lat = 0;
float f11Lon = 0, f11Lat = 0;
- if( !grid->valueAt(indx.lam, indx.phi, f00Lon, f00Lat) ||
- !grid->valueAt(indx.lam + 1, indx.phi, f10Lon, f10Lat) ||
- !grid->valueAt(indx.lam, indx.phi + 1, f01Lon, f01Lat) ||
- !grid->valueAt(indx.lam + 1, indx.phi + 1, f11Lon, f11Lat) )
+ if( !grid->valueAt(indx.lam, indx.phi, compensateNTConvention, f00Lon, f00Lat) ||
+ !grid->valueAt(indx.lam + 1, indx.phi, compensateNTConvention, f10Lon, f10Lat) ||
+ !grid->valueAt(indx.lam, indx.phi + 1, compensateNTConvention, f01Lon, f01Lat) ||
+ !grid->valueAt(indx.lam + 1, indx.phi + 1, compensateNTConvention, f11Lon, f11Lat) )
{
return val;
}
@@ -191,7 +193,8 @@ static PJ_LP nad_intr(PJ_LP t, const HorizontalShiftGrid* grid) {
#define TOL 1e-12
static
-PJ_LP nad_cvt(PJ_LP in, int inverse, const HorizontalShiftGrid* grid) {
+PJ_LP nad_cvt(projCtx ctx, PJ_LP in, int inverse, const HorizontalShiftGrid* grid,
+ const ListOfHGrids& grids) {
PJ_LP t, tb,del, dif;
int i = MAX_ITERATIONS;
const double toltol = TOL*TOL;
@@ -201,42 +204,56 @@ PJ_LP nad_cvt(PJ_LP in, int inverse, const HorizontalShiftGrid* grid) {
/* normalize input to ll origin */
tb = in;
- const auto& extent = grid->extentAndRes();
- tb.lam -= extent.westLon;
- tb.phi -= extent.southLat;
+ const auto* extent = &(grid->extentAndRes());
+ tb.lam -= extent->westLon;
+ tb.phi -= extent->southLat;
tb.lam = adjlon (tb.lam - M_PI) + M_PI;
- t = nad_intr (tb, grid);
+ t = nad_intr (tb, grid, true);
if (t.lam == HUGE_VAL)
return t;
if (!inverse) {
- in.lam -= t.lam;
+ in.lam += t.lam;
in.phi += t.phi;
return in;
}
- t.lam = tb.lam + t.lam;
+ t.lam = tb.lam - t.lam;
t.phi = tb.phi - t.phi;
do {
- del = nad_intr(t, grid);
-
- /* This case used to return failure, but I have
- changed it to return the first order approximation
- of the inverse shift. This avoids cases where the
- grid shift *into* this grid came from another grid.
- While we aren't returning optimally correct results
- I feel a close result in this case is better than
- no result. NFW
- To demonstrate use -112.5839956 49.4914451 against
- the NTv2 grid shift file from Canada. */
+ del = nad_intr(t, grid, true);
+
+ /* We can possibly go outside of the initial guessed grid, so try */
+ /* to fetch a new grid into which iterate... */
if (del.lam == HUGE_VAL)
- break;
+ {
+ PJ_LP lp;
+ lp.lam = t.lam + extent->westLon;
+ lp.phi = t.phi + extent->southLat;
+ auto newGrid = findGrid(grids, lp);
+ if( newGrid == nullptr || newGrid == grid || newGrid->isNullGrid() )
+ break;
+ pj_log(ctx, PJ_LOG_DEBUG_MINOR, "Switching from grid %s to grid %s",
+ grid->name().c_str(),
+ newGrid->name().c_str());
+ grid = newGrid;
+ extent = &(grid->extentAndRes());
+ t.lam = lp.lam - extent->westLon;
+ t.phi = lp.phi - extent->southLat;
+ tb = in;
+ tb.lam -= extent->westLon;
+ tb.phi -= extent->southLat;
+ tb.lam = adjlon (tb.lam - M_PI) + M_PI;
+ dif.lam = std::numeric_limits<double>::max();
+ dif.phi = std::numeric_limits<double>::max();
+ continue;
+ }
- dif.lam = t.lam - del.lam - tb.lam;
- dif.phi = t.phi + del.phi - tb.phi;
+ dif.lam = t.lam + del.lam - tb.lam;
+ dif.phi = t.phi + del.phi - tb.phi;
t.lam -= dif.lam;
t.phi -= dif.phi;
@@ -254,8 +271,8 @@ PJ_LP nad_cvt(PJ_LP in, int inverse, const HorizontalShiftGrid* grid) {
if (del.lam==HUGE_VAL && getenv ("PROJ_DEBUG"))
fprintf (stderr, "Inverse grid shift iteration failed, presumably at grid edge.\nUsing first approximation.\n");
- in.lam = adjlon (t.lam + extent.westLon);
- in.phi = t.phi + extent.southLat;
+ in.lam = adjlon (t.lam + extent->westLon);
+ in.phi = t.phi + extent->southLat;
return in;
}
@@ -280,7 +297,7 @@ PJ_LP proj_hgrid_value(PJ *P, const ListOfHGrids& grids, PJ_LP lp) {
lp.lam = adjlon(lp.lam - M_PI) + M_PI;
- out = nad_intr(lp, grid);
+ out = nad_intr(lp, grid, false);
if (out.lam == HUGE_VAL || out.phi == HUGE_VAL) {
pj_ctx_set_errno(P->ctx, PJD_ERR_GRID_AREA);
@@ -311,7 +328,7 @@ PJ_LP proj_hgrid_apply_internal(PJ_CONTEXT* ctx,
}
int inverse = direction == PJ_FWD ? 0 : 1;
- out = nad_cvt(lp, inverse, grid);
+ out = nad_cvt(ctx, lp, inverse, grid, grids);
if (out.lam == HUGE_VAL || out.phi == HUGE_VAL)
pj_ctx_set_errno(ctx, PJD_ERR_GRID_AREA);
diff --git a/src/apps/projinfo.cpp b/src/apps/projinfo.cpp
index fd9b2f46..f13e526b 100644
--- a/src/apps/projinfo.cpp
+++ b/src/apps/projinfo.cpp
@@ -87,7 +87,8 @@ static void usage() {
<< " [--spatial-test contains|intersects]" << std::endl
<< " [--crs-extent-use none|both|intersection|smallest]"
<< std::endl
- << " [--grid-check none|discard_missing|sort] "
+ << " [--grid-check "
+ "none|discard_missing|sort|known_available] "
"[--show-superseded]"
<< std::endl
<< " [--pivot-crs always|if_no_direct_transformation|"
@@ -509,7 +510,7 @@ static void outputObject(
auto op = dynamic_cast<CoordinateOperation *>(obj.get());
if (op && dbContext && getenv("PROJINFO_NO_GRID_CHECK") == nullptr) {
try {
- auto setGrids = op->gridsNeeded(dbContext);
+ auto setGrids = op->gridsNeeded(dbContext, false);
bool firstWarning = true;
for (const auto &grid : setGrids) {
if (!grid.available) {
@@ -525,6 +526,7 @@ static void outputObject(
if (!grid.url.empty()) {
std::cout << " at " << grid.url;
}
+ std::cout << ", or on CDN";
} else if (!grid.url.empty()) {
std::cout << " Can be obtained at " << grid.url;
}
@@ -539,8 +541,9 @@ static void outputObject(
// ---------------------------------------------------------------------------
-static void outputOperationSummary(const CoordinateOperationNNPtr &op,
- const DatabaseContextPtr &dbContext) {
+static void outputOperationSummary(
+ const CoordinateOperationNNPtr &op, const DatabaseContextPtr &dbContext,
+ CoordinateOperationContext::GridAvailabilityUse gridAvailabilityUse) {
auto ids = op->identifiers();
if (!ids.empty()) {
std::cout << *(ids[0]->codeSpace()) << ":" << ids[0]->code();
@@ -586,10 +589,16 @@ static void outputOperationSummary(const CoordinateOperationNNPtr &op,
if (dbContext && getenv("PROJINFO_NO_GRID_CHECK") == nullptr) {
try {
- auto setGrids = op->gridsNeeded(dbContext);
+ auto setGrids = op->gridsNeeded(dbContext, false);
for (const auto &grid : setGrids) {
if (!grid.available) {
std::cout << ", at least one grid missing";
+ if (gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ KNOWN_AVAILABLE &&
+ !grid.packageName.empty()) {
+ std::cout << " on the system, but available on CDN";
+ }
break;
}
}
@@ -686,7 +695,7 @@ static void outputOperations(
}
if (summary) {
for (const auto &op : list) {
- outputOperationSummary(op, dbContext);
+ outputOperationSummary(op, dbContext, gridAvailabilityUse);
}
} else {
bool first = true;
@@ -701,7 +710,7 @@ static void outputOperations(
"\xC2\xB0"
<< (i + 1) << ":" << std::endl
<< std::endl;
- outputOperationSummary(op, dbContext);
+ outputOperationSummary(op, dbContext, gridAvailabilityUse);
std::cout << std::endl;
outputObject(dbContext, op, allowUseIntermediateCRS, outputOpt);
}
@@ -734,7 +743,9 @@ int main(int argc, char **argv) {
CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST;
bool buildBoundCRSToWGS84 = false;
CoordinateOperationContext::GridAvailabilityUse gridAvailabilityUse =
- CoordinateOperationContext::GridAvailabilityUse::USE_FOR_SORTING;
+ pj_context_is_network_enabled(nullptr)
+ ? CoordinateOperationContext::GridAvailabilityUse::KNOWN_AVAILABLE
+ : CoordinateOperationContext::GridAvailabilityUse::USE_FOR_SORTING;
CoordinateOperationContext::IntermediateCRSUse allowUseIntermediateCRS =
CoordinateOperationContext::IntermediateCRSUse::
IF_NO_DIRECT_TRANSFORMATION;
@@ -947,6 +958,9 @@ int main(int argc, char **argv) {
} else if (ci_equal(value, "sort")) {
gridAvailabilityUse = CoordinateOperationContext::
GridAvailabilityUse::USE_FOR_SORTING;
+ } else if (ci_equal(value, "known_available")) {
+ gridAvailabilityUse = CoordinateOperationContext::
+ GridAvailabilityUse::KNOWN_AVAILABLE;
} else {
std::cerr << "Unrecognized value for option --grid-check: "
<< value << std::endl;
diff --git a/src/ctx.cpp b/src/ctx.cpp
index bcb6e1cc..6172b3c8 100644
--- a/src/ctx.cpp
+++ b/src/ctx.cpp
@@ -33,6 +33,7 @@
#include "proj_experimental.h"
#include "proj_internal.h"
+#include "filemanager.hpp"
/************************************************************************/
/* pj_get_ctx() */
@@ -60,9 +61,9 @@ void pj_set_ctx( projPJ pj, projCtx ctx )
if (pj==nullptr)
return;
pj->ctx = ctx;
- if( pj->is_pipeline )
+ if( pj->reassign_context )
{
- pj_pipeline_assign_context_to_steps(pj, ctx);
+ pj->reassign_context(pj, ctx);
}
for( const auto &alt: pj->alternativeCoordinateOperations )
{
@@ -95,7 +96,8 @@ projCtx_t projCtx_t::createDefault()
projCtx_t ctx;
ctx.debug_level = PJ_LOG_NONE;
ctx.logger = pj_stderr_logger;
- ctx.fileapi = pj_get_default_fileapi();
+ ctx.fileapi_legacy = pj_get_default_fileapi();
+ NS_PROJ::FileManager::fillDefaultNetworkInterface(&ctx);
if( getenv("PROJ_DEBUG") != nullptr )
{
@@ -133,12 +135,13 @@ projCtx_t::projCtx_t(const projCtx_t& other)
debug_level = other.debug_level;
logger = other.logger;
logger_app_data = other.logger_app_data;
- fileapi = other.fileapi;
+ fileapi_legacy = other.fileapi_legacy;
epsg_file_exists = other.epsg_file_exists;
set_search_paths(other.search_paths);
file_finder = other.file_finder;
file_finder_legacy = other.file_finder_legacy;
file_finder_user_data = other.file_finder_user_data;
+ networking = other.networking;
}
/************************************************************************/
@@ -258,28 +261,3 @@ void *pj_ctx_get_app_data( projCtx ctx )
return nullptr;
return ctx->logger_app_data;
}
-
-/************************************************************************/
-/* pj_ctx_set_fileapi() */
-/************************************************************************/
-
-void pj_ctx_set_fileapi( projCtx ctx, projFileAPI *fileapi )
-
-{
- if (nullptr==ctx)
- return;
- ctx->fileapi = fileapi;
-}
-
-/************************************************************************/
-/* pj_ctx_get_fileapi() */
-/************************************************************************/
-
-projFileAPI *pj_ctx_get_fileapi( projCtx ctx )
-
-{
- if (nullptr==ctx)
- return nullptr;
- return ctx->fileapi;
-}
-
diff --git a/src/fileapi.cpp b/src/fileapi.cpp
index 70c7b5de..70be2502 100644
--- a/src/fileapi.cpp
+++ b/src/fileapi.cpp
@@ -34,6 +34,7 @@
#include "proj.h"
#include "proj_internal.h"
+#include "filemanager.hpp"
static PAFile stdio_fopen(projCtx ctx, const char *filename,
const char *access);
@@ -141,7 +142,7 @@ static void stdio_fclose(PAFile file)
PAFile pj_ctx_fopen(projCtx ctx, const char *filename, const char *access)
{
- return ctx->fileapi->FOpen(ctx, filename, access);
+ return ctx->fileapi_legacy->FOpen(ctx, filename, access);
}
/************************************************************************/
@@ -149,7 +150,7 @@ PAFile pj_ctx_fopen(projCtx ctx, const char *filename, const char *access)
/************************************************************************/
size_t pj_ctx_fread(projCtx ctx, void *buffer, size_t size, size_t nmemb, PAFile file)
{
- return ctx->fileapi->FRead(buffer, size, nmemb, file);
+ return ctx->fileapi_legacy->FRead(buffer, size, nmemb, file);
}
/************************************************************************/
@@ -157,7 +158,7 @@ size_t pj_ctx_fread(projCtx ctx, void *buffer, size_t size, size_t nmemb, PAFile
/************************************************************************/
int pj_ctx_fseek(projCtx ctx, PAFile file, long offset, int whence)
{
- return ctx->fileapi->FSeek(file, offset, whence);
+ return ctx->fileapi_legacy->FSeek(file, offset, whence);
}
/************************************************************************/
@@ -165,7 +166,7 @@ int pj_ctx_fseek(projCtx ctx, PAFile file, long offset, int whence)
/************************************************************************/
long pj_ctx_ftell(projCtx ctx, PAFile file)
{
- return ctx->fileapi->FTell(file);
+ return ctx->fileapi_legacy->FTell(file);
}
/************************************************************************/
@@ -173,7 +174,7 @@ long pj_ctx_ftell(projCtx ctx, PAFile file)
/************************************************************************/
void pj_ctx_fclose(projCtx ctx, PAFile file)
{
- ctx->fileapi->FClose(file);
+ ctx->fileapi_legacy->FClose(file);
}
/************************************************************************/
@@ -212,3 +213,28 @@ char *pj_ctx_fgets(projCtx ctx, char *line, int size, PAFile file)
}
return line;
}
+
+/************************************************************************/
+/* pj_ctx_set_fileapi() */
+/************************************************************************/
+
+void pj_ctx_set_fileapi( projCtx ctx, projFileAPI *fileapi )
+
+{
+ if (nullptr==ctx)
+ return;
+ ctx->fileapi_legacy = fileapi;
+}
+
+/************************************************************************/
+/* pj_ctx_get_fileapi() */
+/************************************************************************/
+
+projFileAPI *pj_ctx_get_fileapi( projCtx ctx )
+
+{
+ if (nullptr==ctx)
+ return nullptr;
+ return ctx->fileapi_legacy;
+}
+
diff --git a/src/filemanager.cpp b/src/filemanager.cpp
new file mode 100644
index 00000000..625dca03
--- /dev/null
+++ b/src/filemanager.cpp
@@ -0,0 +1,997 @@
+/******************************************************************************
+ * Project: PROJ
+ * Purpose: File manager
+ * Author: Even Rouault, <even.rouault at spatialys.com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2019, Even Rouault, <even.rouault at spatialys.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *****************************************************************************/
+
+#ifndef FROM_PROJ_CPP
+#define FROM_PROJ_CPP
+#endif
+#define LRU11_DO_NOT_DEFINE_OUT_OF_CLASS_METHODS
+
+#include <algorithm>
+#include <functional>
+#include <limits>
+
+#include "filemanager.hpp"
+#include "proj.h"
+#include "proj/internal/internal.hpp"
+#include "proj/internal/lru_cache.hpp"
+#include "proj_internal.h"
+
+#ifdef __MINGW32__
+// mingw32-win32 doesn't implement std::mutex
+namespace {
+class MyMutex {
+ public:
+ // cppcheck-suppress functionStatic
+ void lock() { pj_acquire_lock(); }
+ // cppcheck-suppress functionStatic
+ void unlock() { pj_release_lock(); }
+};
+}
+#else
+#include <mutex>
+#define MyMutex std::mutex
+#endif
+
+#ifdef CURL_ENABLED
+#include <curl/curl.h>
+#include <sqlite3.h> // for sqlite3_snprintf
+#endif
+
+#if defined(__linux)
+#include <unistd.h>
+#elif defined(_WIN32)
+#include <windows.h>
+#elif defined(__MACH__) && defined(__APPLE__)
+#include <mach-o/dyld.h>
+#elif defined(__FreeBSD__)
+#include <sys/sysctl.h>
+#include <sys/types.h>
+#endif
+
+//! @cond Doxygen_Suppress
+
+#define STR_HELPER(x) #x
+#define STR(x) STR_HELPER(x)
+
+using namespace NS_PROJ::internal;
+
+NS_PROJ_START
+
+// ---------------------------------------------------------------------------
+
+File::File(const std::string &name) : name_(name) {}
+
+// ---------------------------------------------------------------------------
+
+File::~File() = default;
+
+// ---------------------------------------------------------------------------
+
+class FileStdio : public File {
+ PJ_CONTEXT *m_ctx;
+ FILE *m_fp;
+
+ FileStdio(const FileStdio &) = delete;
+ FileStdio &operator=(const FileStdio &) = delete;
+
+ protected:
+ FileStdio(const std::string &name, PJ_CONTEXT *ctx, FILE *fp)
+ : File(name), m_ctx(ctx), m_fp(fp) {}
+
+ public:
+ ~FileStdio() override;
+
+ size_t read(void *buffer, size_t sizeBytes) override;
+ bool seek(unsigned long long offset, int whence = SEEK_SET) override;
+ unsigned long long tell() override;
+ void reassign_context(PJ_CONTEXT *ctx) override { m_ctx = ctx; }
+
+ static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename);
+};
+
+// ---------------------------------------------------------------------------
+
+FileStdio::~FileStdio() { fclose(m_fp); }
+
+// ---------------------------------------------------------------------------
+
+size_t FileStdio::read(void *buffer, size_t sizeBytes) {
+ return fread(buffer, 1, sizeBytes, m_fp);
+}
+
+// ---------------------------------------------------------------------------
+
+bool FileStdio::seek(unsigned long long offset, int whence) {
+ // TODO one day: use 64-bit offset compatible API
+ if (offset != static_cast<unsigned long long>(static_cast<long>(offset))) {
+ pj_log(m_ctx, PJ_LOG_ERROR,
+ "Attempt at seeking to a 64 bit offset. Not supported yet");
+ return false;
+ }
+ return fseek(m_fp, static_cast<long>(offset), whence) == 0;
+}
+
+// ---------------------------------------------------------------------------
+
+unsigned long long FileStdio::tell() {
+ // TODO one day: use 64-bit offset compatible API
+ return ftell(m_fp);
+}
+
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<File> FileStdio::open(PJ_CONTEXT *ctx, const char *filename) {
+ auto fp = fopen(filename, "rb");
+ return std::unique_ptr<File>(fp ? new FileStdio(filename, ctx, fp)
+ : nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+#ifndef REMOVE_LEGACY_SUPPORT
+
+class FileLegacyAdapter : public File {
+ PJ_CONTEXT *m_ctx;
+ PAFile m_fp;
+
+ FileLegacyAdapter(const FileLegacyAdapter &) = delete;
+ FileLegacyAdapter &operator=(const FileLegacyAdapter &) = delete;
+
+ protected:
+ FileLegacyAdapter(const std::string &name, PJ_CONTEXT *ctx, PAFile fp)
+ : File(name), m_ctx(ctx), m_fp(fp) {}
+
+ public:
+ ~FileLegacyAdapter() override;
+
+ size_t read(void *buffer, size_t sizeBytes) override;
+ bool seek(unsigned long long offset, int whence = SEEK_SET) override;
+ unsigned long long tell() override;
+ void reassign_context(PJ_CONTEXT *ctx) override { m_ctx = ctx; }
+
+ static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename);
+};
+
+// ---------------------------------------------------------------------------
+
+FileLegacyAdapter::~FileLegacyAdapter() { pj_ctx_fclose(m_ctx, m_fp); }
+
+// ---------------------------------------------------------------------------
+
+size_t FileLegacyAdapter::read(void *buffer, size_t sizeBytes) {
+ return pj_ctx_fread(m_ctx, buffer, 1, sizeBytes, m_fp);
+}
+
+// ---------------------------------------------------------------------------
+
+bool FileLegacyAdapter::seek(unsigned long long offset, int whence) {
+ if (offset != static_cast<unsigned long long>(static_cast<long>(offset))) {
+ pj_log(m_ctx, PJ_LOG_ERROR,
+ "Attempt at seeking to a 64 bit offset. Not supported yet");
+ return false;
+ }
+ return pj_ctx_fseek(m_ctx, m_fp, static_cast<long>(offset), whence) == 0;
+}
+
+// ---------------------------------------------------------------------------
+
+unsigned long long FileLegacyAdapter::tell() {
+ return pj_ctx_ftell(m_ctx, m_fp);
+}
+
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<File> FileLegacyAdapter::open(PJ_CONTEXT *ctx,
+ const char *filename) {
+ auto fid = pj_ctx_fopen(ctx, filename, "rb");
+ return std::unique_ptr<File>(fid ? new FileLegacyAdapter(filename, ctx, fid)
+ : nullptr);
+}
+
+#endif // REMOVE_LEGACY_SUPPORT
+
+// ---------------------------------------------------------------------------
+
+constexpr size_t DOWNLOAD_CHUNK_SIZE = 16 * 1024;
+constexpr int MAX_CHUNKS = 64;
+
+class NetworkChunkCache {
+ public:
+ void insert(const std::string &url, unsigned long long chunkIdx,
+ std::vector<unsigned char> &&data);
+
+ std::shared_ptr<std::vector<unsigned char>>
+ get(const std::string &url, unsigned long long chunkIdx);
+
+ void clear();
+
+ private:
+ struct Key {
+ std::string url;
+ unsigned long long chunkIdx;
+
+ Key(const std::string &urlIn, unsigned long long chunkIdxIn)
+ : url(urlIn), chunkIdx(chunkIdxIn) {}
+ bool operator==(const Key &other) const {
+ return url == other.url && chunkIdx == other.chunkIdx;
+ }
+ };
+
+ struct KeyHasher {
+ std::size_t operator()(const Key &k) const {
+ return std::hash<std::string>{}(k.url) ^
+ (std::hash<unsigned long long>{}(k.chunkIdx) << 1);
+ }
+ };
+
+ lru11::Cache<
+ Key, std::shared_ptr<std::vector<unsigned char>>, MyMutex,
+ std::unordered_map<
+ Key,
+ typename std::list<lru11::KeyValuePair<
+ Key, std::shared_ptr<std::vector<unsigned char>>>>::iterator,
+ KeyHasher>>
+ cache_{MAX_CHUNKS};
+};
+
+// ---------------------------------------------------------------------------
+
+void NetworkChunkCache::insert(const std::string &url,
+ unsigned long long chunkIdx,
+ std::vector<unsigned char> &&data) {
+ cache_.insert(
+ Key(url, chunkIdx),
+ std::make_shared<std::vector<unsigned char>>(std::move(data)));
+}
+
+// ---------------------------------------------------------------------------
+
+std::shared_ptr<std::vector<unsigned char>>
+NetworkChunkCache::get(const std::string &url, unsigned long long chunkIdx) {
+ std::shared_ptr<std::vector<unsigned char>> ret;
+ cache_.tryGet(Key(url, chunkIdx), ret);
+ return ret;
+}
+
+// ---------------------------------------------------------------------------
+
+void NetworkChunkCache::clear() { cache_.clear(); }
+
+// ---------------------------------------------------------------------------
+
+static NetworkChunkCache gNetworkChunkCache{};
+
+struct FileProperties {
+ unsigned long long size;
+};
+
+static lru11::Cache<std::string, FileProperties, MyMutex>
+ gNetworkFileProperties{};
+
+// ---------------------------------------------------------------------------
+
+class NetworkFile : public File {
+ PJ_CONTEXT *m_ctx;
+ std::string m_url;
+ PROJ_NETWORK_HANDLE *m_handle;
+ unsigned long long m_pos = 0;
+ size_t m_nBlocksToDownload = 1;
+ unsigned long long m_lastDownloadedOffset;
+ unsigned long long m_filesize;
+ proj_network_close_cbk_type m_closeCbk;
+
+ NetworkFile(const NetworkFile &) = delete;
+ NetworkFile &operator=(const NetworkFile &) = delete;
+
+ protected:
+ NetworkFile(PJ_CONTEXT *ctx, const std::string &url,
+ PROJ_NETWORK_HANDLE *handle,
+ unsigned long long lastDownloadOffset,
+ unsigned long long filesize)
+ : File(url), m_ctx(ctx), m_url(url), m_handle(handle),
+ m_lastDownloadedOffset(lastDownloadOffset), m_filesize(filesize),
+ m_closeCbk(ctx->networking.close) {}
+
+ public:
+ ~NetworkFile() override;
+
+ size_t read(void *buffer, size_t sizeBytes) override;
+ bool seek(unsigned long long offset, int whence) override;
+ unsigned long long tell() override;
+ void reassign_context(PJ_CONTEXT *ctx) override;
+
+ static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename);
+};
+
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<File> NetworkFile::open(PJ_CONTEXT *ctx, const char *filename) {
+ if (gNetworkChunkCache.get(filename, 0)) {
+ unsigned long long filesize = 0;
+ FileProperties props;
+ if (gNetworkFileProperties.tryGet(filename, props)) {
+ filesize = props.size;
+ }
+ return std::unique_ptr<File>(new NetworkFile(
+ ctx, filename, nullptr,
+ std::numeric_limits<unsigned long long>::max(), filesize));
+ } else {
+ std::vector<unsigned char> buffer(DOWNLOAD_CHUNK_SIZE);
+ size_t size_read = 0;
+ std::string errorBuffer;
+ errorBuffer.resize(1024);
+
+ auto handle = ctx->networking.open(
+ ctx, filename, 0, buffer.size(), &buffer[0], &size_read,
+ errorBuffer.size(), &errorBuffer[0], ctx->networking.user_data);
+ buffer.resize(size_read);
+ gNetworkChunkCache.insert(filename, 0, std::move(buffer));
+ if (!handle) {
+ errorBuffer.resize(strlen(errorBuffer.data()));
+ pj_log(ctx, PJ_LOG_ERROR, "Cannot open %s: %s", filename,
+ errorBuffer.c_str());
+ }
+
+ unsigned long long filesize = 0;
+ if (handle) {
+ const char *contentRange = ctx->networking.get_header_value(
+ ctx, handle, "Content-Range", ctx->networking.user_data);
+ if (contentRange) {
+ const char *slash = strchr(contentRange, '/');
+ if (slash) {
+ filesize = std::stoull(slash + 1);
+ FileProperties props;
+ props.size = filesize;
+ gNetworkFileProperties.insert(filename, props);
+ }
+ }
+ }
+
+ return std::unique_ptr<File>(
+ handle ? new NetworkFile(ctx, filename, handle, size_read, filesize)
+ : nullptr);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+size_t NetworkFile::read(void *buffer, size_t sizeBytes) {
+
+ if (sizeBytes == 0)
+ return 0;
+
+ auto iterOffset = m_pos;
+ while (sizeBytes) {
+ const auto chunkIdxToDownload = iterOffset / DOWNLOAD_CHUNK_SIZE;
+ const auto offsetToDownload = chunkIdxToDownload * DOWNLOAD_CHUNK_SIZE;
+ std::vector<unsigned char> region;
+ auto pChunk = gNetworkChunkCache.get(m_url, chunkIdxToDownload);
+ if (pChunk != nullptr) {
+ region = *pChunk;
+ } else {
+ if (offsetToDownload == m_lastDownloadedOffset) {
+ // In case of consecutive reads (of small size), we use a
+ // heuristic that we will read the file sequentially, so
+ // we double the requested size to decrease the number of
+ // client/server roundtrips.
+ if (m_nBlocksToDownload < 100)
+ m_nBlocksToDownload *= 2;
+ } else {
+ // Random reads. Cancel the above heuristics.
+ m_nBlocksToDownload = 1;
+ }
+
+ // Ensure that we will request at least the number of blocks
+ // to satisfy the remaining buffer size to read.
+ const auto endOffsetToDownload =
+ ((iterOffset + sizeBytes + DOWNLOAD_CHUNK_SIZE - 1) /
+ DOWNLOAD_CHUNK_SIZE) *
+ DOWNLOAD_CHUNK_SIZE;
+ const auto nMinBlocksToDownload = static_cast<size_t>(
+ (endOffsetToDownload - offsetToDownload) / DOWNLOAD_CHUNK_SIZE);
+ if (m_nBlocksToDownload < nMinBlocksToDownload)
+ m_nBlocksToDownload = nMinBlocksToDownload;
+
+ // Avoid reading already cached data.
+ // Note: this might get evicted if concurrent reads are done, but
+ // this should not cause bugs. Just missed optimization.
+ for (size_t i = 1; i < m_nBlocksToDownload; i++) {
+ if (gNetworkChunkCache.get(m_url, chunkIdxToDownload + i) !=
+ nullptr) {
+ m_nBlocksToDownload = i;
+ break;
+ }
+ }
+
+ if (m_nBlocksToDownload > MAX_CHUNKS)
+ m_nBlocksToDownload = MAX_CHUNKS;
+
+ region.resize(m_nBlocksToDownload * DOWNLOAD_CHUNK_SIZE);
+ size_t nRead = 0;
+ std::string errorBuffer;
+ errorBuffer.resize(1024);
+ if (!m_handle) {
+ m_handle = m_ctx->networking.open(
+ m_ctx, m_url.c_str(), offsetToDownload,
+ m_nBlocksToDownload * DOWNLOAD_CHUNK_SIZE, &region[0],
+ &nRead, errorBuffer.size(), &errorBuffer[0],
+ m_ctx->networking.user_data);
+ if (!m_handle) {
+ return 0;
+ }
+ } else {
+ nRead = m_ctx->networking.read_range(
+ m_ctx, m_handle, offsetToDownload,
+ m_nBlocksToDownload * DOWNLOAD_CHUNK_SIZE, &region[0],
+ errorBuffer.size(), &errorBuffer[0],
+ m_ctx->networking.user_data);
+ }
+ if (nRead == 0) {
+ errorBuffer.resize(strlen(errorBuffer.data()));
+ if (!errorBuffer.empty()) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Cannot read in %s: %s",
+ m_url.c_str(), errorBuffer.c_str());
+ }
+ return 0;
+ }
+ region.resize(nRead);
+ m_lastDownloadedOffset = offsetToDownload + nRead;
+
+ const auto nChunks =
+ (region.size() + DOWNLOAD_CHUNK_SIZE - 1) / DOWNLOAD_CHUNK_SIZE;
+ for (size_t i = 0; i < nChunks; i++) {
+ std::vector<unsigned char> chunk(
+ region.data() + i * DOWNLOAD_CHUNK_SIZE,
+ region.data() +
+ std::min((i + 1) * DOWNLOAD_CHUNK_SIZE, region.size()));
+ gNetworkChunkCache.insert(m_url, chunkIdxToDownload + i,
+ std::move(chunk));
+ }
+ }
+ const size_t nToCopy = static_cast<size_t>(
+ std::min(static_cast<unsigned long long>(sizeBytes),
+ region.size() - (iterOffset - offsetToDownload)));
+ memcpy(buffer, region.data() + iterOffset - offsetToDownload, nToCopy);
+ buffer = static_cast<char *>(buffer) + nToCopy;
+ iterOffset += nToCopy;
+ sizeBytes -= nToCopy;
+ if (region.size() < static_cast<size_t>(DOWNLOAD_CHUNK_SIZE) &&
+ sizeBytes != 0) {
+ break;
+ }
+ }
+
+ size_t nRead = static_cast<size_t>(iterOffset - m_pos);
+ m_pos = iterOffset;
+ return nRead;
+}
+
+// ---------------------------------------------------------------------------
+
+bool NetworkFile::seek(unsigned long long offset, int whence) {
+ if (whence == SEEK_SET) {
+ m_pos = offset;
+ } else if (whence == SEEK_CUR) {
+ m_pos += offset;
+ } else {
+ if (offset != 0)
+ return false;
+ m_pos = m_filesize;
+ }
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+unsigned long long NetworkFile::tell() { return m_pos; }
+
+// ---------------------------------------------------------------------------
+
+NetworkFile::~NetworkFile() {
+ if (m_handle) {
+ m_ctx->networking.close(m_ctx, m_handle, m_ctx->networking.user_data);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+void NetworkFile::reassign_context(PJ_CONTEXT *ctx) {
+ m_ctx = ctx;
+ if (m_closeCbk != m_ctx->networking.close) {
+ pj_log(m_ctx, PJ_LOG_ERROR,
+ "Networking close callback has changed following context "
+ "reassignment ! This is highly suspicious");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<File> FileManager::open(PJ_CONTEXT *ctx, const char *filename) {
+#ifndef REMOVE_LEGACY_SUPPORT
+ // If the user has specified a legacy fileapi, use it
+ if (ctx->fileapi_legacy != pj_get_default_fileapi()) {
+ return FileLegacyAdapter::open(ctx, filename);
+ }
+#endif
+ if (starts_with(filename, "http://") || starts_with(filename, "https://")) {
+ if (!pj_context_is_network_enabled(ctx)) {
+ pj_log(
+ ctx, PJ_LOG_ERROR,
+ "Attempt at accessing remote resource not authorized. Either "
+ "set PROJ_NETWORK=ON or "
+ "proj_context_set_enable_network(ctx, TRUE)");
+ return nullptr;
+ }
+ return NetworkFile::open(ctx, filename);
+ }
+ return FileStdio::open(ctx, filename);
+}
+
+// ---------------------------------------------------------------------------
+
+#ifdef CURL_ENABLED
+
+struct CurlFileHandle {
+ std::string m_url;
+ CURL *m_handle;
+ std::string m_headers{};
+ std::string m_lastval{};
+ std::string m_useragent{};
+ char m_szCurlErrBuf[CURL_ERROR_SIZE + 1] = {};
+
+ CurlFileHandle(const CurlFileHandle &) = delete;
+ CurlFileHandle &operator=(const CurlFileHandle &) = delete;
+
+ explicit CurlFileHandle(const char *url, CURL *handle);
+ ~CurlFileHandle();
+
+ static PROJ_NETWORK_HANDLE *
+ open(PJ_CONTEXT *, const char *url, unsigned long long offset,
+ size_t size_to_read, void *buffer, size_t *out_size_read,
+ size_t error_string_max_size, char *out_error_string, void *);
+};
+
+// ---------------------------------------------------------------------------
+
+static std::string GetExecutableName() {
+#if defined(__linux)
+ std::string path;
+ path.resize(1024);
+ const auto ret = readlink("/proc/self/exe", &path[0], path.size());
+ if (ret > 0) {
+ path.resize(ret);
+ const auto pos = path.rfind('/');
+ if (pos != std::string::npos) {
+ path = path.substr(pos + 1);
+ }
+ return path;
+ }
+#elif defined(_WIN32)
+ std::string path;
+ path.resize(1024);
+ if (GetModuleFileNameA(nullptr, &path[0],
+ static_cast<DWORD>(path.size()))) {
+ path.resize(strlen(path.c_str()));
+ const auto pos = path.rfind('\\');
+ if (pos != std::string::npos) {
+ path = path.substr(pos + 1);
+ }
+ return path;
+ }
+#elif defined(__MACH__) && defined(__APPLE__)
+ std::string path;
+ path.resize(1024);
+ uint32_t size = static_cast<uint32_t>(path.size());
+ if (_NSGetExecutablePath(&path[0], &size) == 0) {
+ path.resize(strlen(path.c_str()));
+ const auto pos = path.rfind('/');
+ if (pos != std::string::npos) {
+ path = path.substr(pos + 1);
+ }
+ return path;
+ }
+#elif defined(__FreeBSD__)
+ int mib[4];
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC;
+ mib[2] = KERN_PROC_PATHNAME;
+ mib[3] = -1;
+ std::string path;
+ path.resize(1024);
+ size_t size = path.size();
+ if (sysctl(mib, 4, &path[0], &size, nullptr, 0) == 0) {
+ path.resize(strlen(path.c_str()));
+ const auto pos = path.rfind('/');
+ if (pos != std::string::npos) {
+ path = path.substr(pos + 1);
+ }
+ return path;
+ }
+#endif
+
+ return std::string();
+}
+
+// ---------------------------------------------------------------------------
+
+CurlFileHandle::CurlFileHandle(const char *url, CURL *handle)
+ : m_url(url), m_handle(handle) {
+ curl_easy_setopt(handle, CURLOPT_URL, m_url.c_str());
+
+ if (getenv("PROJ_CURL_VERBOSE"))
+ curl_easy_setopt(handle, CURLOPT_VERBOSE, 1);
+
+// CURLOPT_SUPPRESS_CONNECT_HEADERS is defined in curl 7.54.0 or newer.
+#if LIBCURL_VERSION_NUM >= 0x073600
+ curl_easy_setopt(handle, CURLOPT_SUPPRESS_CONNECT_HEADERS, 1L);
+#endif
+
+ // Enable following redirections. Requires libcurl 7.10.1 at least.
+ curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1);
+ curl_easy_setopt(handle, CURLOPT_MAXREDIRS, 10);
+
+ if (getenv("PROJ_UNSAFE_SSL")) {
+ curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0L);
+ curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 0L);
+ }
+
+ curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, m_szCurlErrBuf);
+
+ if (getenv("PROJ_NO_USERAGENT") == nullptr) {
+ m_useragent = "PROJ " STR(PROJ_VERSION_MAJOR) "." STR(
+ PROJ_VERSION_MINOR) "." STR(PROJ_VERSION_PATCH);
+ const auto exeName = GetExecutableName();
+ if (!exeName.empty()) {
+ m_useragent = exeName + " using " + m_useragent;
+ }
+ curl_easy_setopt(handle, CURLOPT_USERAGENT, m_useragent.data());
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+CurlFileHandle::~CurlFileHandle() { curl_easy_cleanup(m_handle); }
+
+// ---------------------------------------------------------------------------
+
+static size_t pj_curl_write_func(void *buffer, size_t count, size_t nmemb,
+ void *req) {
+ const size_t nSize = count * nmemb;
+ auto pStr = static_cast<std::string *>(req);
+ if (pStr->size() + nSize > pStr->capacity()) {
+ // to avoid servers not honouring Range to cause excessive memory
+ // allocation
+ return 0;
+ }
+ pStr->append(static_cast<const char *>(buffer), nSize);
+ return nmemb;
+}
+
+// ---------------------------------------------------------------------------
+
+PROJ_NETWORK_HANDLE *CurlFileHandle::open(PJ_CONTEXT *, const char *url,
+ unsigned long long offset,
+ size_t size_to_read, void *buffer,
+ size_t *out_size_read,
+ size_t error_string_max_size,
+ char *out_error_string, void *) {
+ CURL *hCurlHandle = curl_easy_init();
+ if (!hCurlHandle)
+ return nullptr;
+
+ auto file =
+ std::unique_ptr<CurlFileHandle>(new CurlFileHandle(url, hCurlHandle));
+
+ char szBuffer[128];
+ sqlite3_snprintf(sizeof(szBuffer), szBuffer, "%llu-%llu", offset,
+ offset + size_to_read - 1);
+ curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, szBuffer);
+
+ std::string headers;
+ headers.reserve(16 * 1024);
+ curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA, &headers);
+ curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION, pj_curl_write_func);
+
+ std::string body;
+ body.reserve(size_to_read);
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &body);
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION, pj_curl_write_func);
+
+ file->m_szCurlErrBuf[0] = '\0';
+
+ curl_easy_perform(hCurlHandle);
+
+ long response_code = 0;
+ curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
+
+ curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA, nullptr);
+ curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION, nullptr);
+
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, nullptr);
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION, nullptr);
+
+ if (response_code == 0 || response_code >= 300) {
+ if (out_error_string) {
+ if (file->m_szCurlErrBuf[0]) {
+ snprintf(out_error_string, error_string_max_size, "%s",
+ file->m_szCurlErrBuf);
+ } else {
+ snprintf(out_error_string, error_string_max_size,
+ "HTTP error %ld: %s", response_code, body.c_str());
+ }
+ }
+ return nullptr;
+ }
+ if (out_error_string && error_string_max_size) {
+ out_error_string[0] = '\0';
+ }
+
+ if (!body.empty()) {
+ memcpy(buffer, body.data(), std::min(size_to_read, body.size()));
+ }
+ *out_size_read = std::min(size_to_read, body.size());
+
+ file->m_headers = std::move(headers);
+ return reinterpret_cast<PROJ_NETWORK_HANDLE *>(file.release());
+}
+
+// ---------------------------------------------------------------------------
+
+static void pj_curl_close(PJ_CONTEXT *, PROJ_NETWORK_HANDLE *handle,
+ void * /*user_data*/) {
+ delete reinterpret_cast<CurlFileHandle *>(handle);
+}
+
+// ---------------------------------------------------------------------------
+
+static size_t pj_curl_read_range(PJ_CONTEXT *, PROJ_NETWORK_HANDLE *raw_handle,
+ unsigned long long offset, size_t size_to_read,
+ void *buffer, size_t error_string_max_size,
+ char *out_error_string, void *) {
+ auto handle = reinterpret_cast<CurlFileHandle *>(raw_handle);
+ auto hCurlHandle = handle->m_handle;
+
+ char szBuffer[128];
+ sqlite3_snprintf(sizeof(szBuffer), szBuffer, "%llu-%llu", offset,
+ offset + size_to_read - 1);
+ curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, szBuffer);
+
+ std::string body;
+ body.reserve(size_to_read);
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &body);
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION, pj_curl_write_func);
+
+ handle->m_szCurlErrBuf[0] = '\0';
+
+ curl_easy_perform(hCurlHandle);
+
+ long response_code = 0;
+ curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
+
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, nullptr);
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION, nullptr);
+
+ if (response_code == 0 || response_code >= 300) {
+ if (out_error_string) {
+ if (handle->m_szCurlErrBuf[0]) {
+ snprintf(out_error_string, error_string_max_size, "%s",
+ handle->m_szCurlErrBuf);
+ } else {
+ snprintf(out_error_string, error_string_max_size,
+ "HTTP error %ld: %s", response_code, body.c_str());
+ }
+ }
+ return 0;
+ }
+ if (out_error_string && error_string_max_size) {
+ out_error_string[0] = '\0';
+ }
+
+ if (!body.empty()) {
+ memcpy(buffer, body.data(), std::min(size_to_read, body.size()));
+ }
+ return std::min(size_to_read, body.size());
+}
+
+// ---------------------------------------------------------------------------
+
+static const char *pj_curl_get_header_value(PJ_CONTEXT *,
+ PROJ_NETWORK_HANDLE *raw_handle,
+ const char *header_name, void *) {
+ auto handle = reinterpret_cast<CurlFileHandle *>(raw_handle);
+ auto pos = ci_find(handle->m_headers, header_name);
+ if (pos == std::string::npos)
+ return nullptr;
+ const char *c_str = handle->m_headers.c_str();
+ if (c_str[pos] == ':')
+ pos++;
+ while (c_str[pos] == ' ')
+ pos++;
+ auto posEnd = pos;
+ while (c_str[posEnd] != '\n' && c_str[posEnd] != '\0')
+ posEnd++;
+ handle->m_lastval = handle->m_headers.substr(pos, posEnd - pos);
+ return handle->m_lastval.c_str();
+}
+
+#else
+
+// ---------------------------------------------------------------------------
+
+static PROJ_NETWORK_HANDLE *
+no_op_network_open(PJ_CONTEXT *ctx, const char * /* url */,
+ unsigned long long, /* offset */
+ size_t, /* size to read */
+ void *, /* buffer to update with bytes read*/
+ size_t *, /* output: size actually read */
+ size_t error_string_max_size, char *out_error_string,
+ void * /*user_data*/) {
+ if (out_error_string) {
+ snprintf(out_error_string, error_string_max_size, "%s",
+ "Network functionality not available");
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+static void no_op_network_close(PJ_CONTEXT *, PROJ_NETWORK_HANDLE *,
+ void * /*user_data*/) {}
+
+#endif
+
+// ---------------------------------------------------------------------------
+
+void FileManager::fillDefaultNetworkInterface(PJ_CONTEXT *ctx) {
+#ifdef CURL_ENABLED
+ ctx->networking.open = CurlFileHandle::open;
+ ctx->networking.close = pj_curl_close;
+ ctx->networking.read_range = pj_curl_read_range;
+ ctx->networking.get_header_value = pj_curl_get_header_value;
+#else
+ ctx->networking.open = no_op_network_open;
+ ctx->networking.close = no_op_network_close;
+#endif
+}
+
+// ---------------------------------------------------------------------------
+
+void FileManager::clearCache() {
+ gNetworkChunkCache.clear();
+ gNetworkFileProperties.clear();
+}
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_END
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** Define a custom set of callbacks for network access.
+ *
+ * All callbacks should be provided (non NULL pointers).
+ *
+ * @param ctx PROJ context, or NULL
+ * @param open_cbk Callback to open a remote file given its URL
+ * @param close_cbk Callback to close a remote file.
+ * @param get_header_value_cbk Callback to get HTTP headers
+ * @param read_range_cbk Callback to read a range of bytes inside a remote file.
+ * @param user_data Arbitrary pointer provided by the user, and passed to the
+ * above callbacks. May be NULL.
+ * @return TRUE in case of success.
+ */
+int proj_context_set_network_callbacks(
+ PJ_CONTEXT *ctx, proj_network_open_cbk_type open_cbk,
+ proj_network_close_cbk_type close_cbk,
+ proj_network_get_header_value_cbk_type get_header_value_cbk,
+ proj_network_read_range_type read_range_cbk, void *user_data) {
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+ if (!open_cbk || !close_cbk || !get_header_value_cbk || !read_range_cbk) {
+ return false;
+ }
+ ctx->networking.open = open_cbk;
+ ctx->networking.close = close_cbk;
+ ctx->networking.get_header_value = get_header_value_cbk;
+ ctx->networking.read_range = read_range_cbk;
+ ctx->networking.user_data = user_data;
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+/** Enable or disable network access.
+*
+* This overrides the default endpoint in the PROJ configuration file or with
+* the PROJ_NETWORK environment variable.
+*
+* @param ctx PROJ context, or NULL
+* @param enable TRUE if network access is allowed.
+* @return TRUE if network access is possible. That is either libcurl is
+* available, or an alternate interface has been set.
+*/
+int proj_context_set_enable_network(PJ_CONTEXT *ctx, int enable) {
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+ // Load ini file, now so as to override its network settings
+ pj_load_ini(ctx);
+ ctx->networking.enabled_env_variable_checked = true;
+ ctx->networking.enabled = enable != FALSE;
+#ifdef CURL_ENABLED
+ return ctx->networking.enabled;
+#else
+ return ctx->networking.enabled &&
+ ctx->networking.open != NS_PROJ::no_op_network_open;
+#endif
+}
+
+// ---------------------------------------------------------------------------
+
+/** Define the URL endpoint to query for remote grids.
+*
+* This overrides the default endpoint in the PROJ configuration file or with
+* the PROJ_NETWORK_ENDPOINT environment variable.
+*
+* @param ctx PROJ context, or NULL
+* @param url Endpoint URL. Must NOT be NULL.
+*/
+void proj_context_set_url_endpoint(PJ_CONTEXT *ctx, const char *url) {
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+ // Load ini file, now so as to override its network settings
+ pj_load_ini(ctx);
+ ctx->endpoint = url;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+bool pj_context_is_network_enabled(PJ_CONTEXT *ctx) {
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+ if (ctx->networking.enabled_env_variable_checked) {
+ return ctx->networking.enabled;
+ }
+ const char *enabled = getenv("PROJ_NETWORK");
+ if (enabled && enabled[0] != '\0') {
+ ctx->networking.enabled = ci_equal(enabled, "ON") ||
+ ci_equal(enabled, "YES") ||
+ ci_equal(enabled, "TRUE");
+ }
+ pj_load_ini(ctx);
+ ctx->networking.enabled_env_variable_checked = true;
+ return ctx->networking.enabled;
+}
+
+//! @endcond
diff --git a/src/filemanager.hpp b/src/filemanager.hpp
new file mode 100644
index 00000000..972634c2
--- /dev/null
+++ b/src/filemanager.hpp
@@ -0,0 +1,80 @@
+/******************************************************************************
+ * Project: PROJ
+ * Purpose: File manager
+ * Author: Even Rouault, <even.rouault at spatialys.com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2019, Even Rouault, <even.rouault at spatialys.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *****************************************************************************/
+
+#ifndef FILEMANAGER_HPP_INCLUDED
+#define FILEMANAGER_HPP_INCLUDED
+
+#include <memory>
+
+#include "proj.h"
+#include "proj/util.hpp"
+
+NS_PROJ_START
+
+//! @cond Doxygen_Suppress
+
+class File;
+
+class FileManager {
+ private:
+ FileManager() = delete;
+
+ public:
+ // "Low-level" interface.
+ static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename);
+
+ // "High-level" interface, honoring PROJ_LIB and the like.
+ static std::unique_ptr<File> open_resource_file(PJ_CONTEXT *ctx,
+ const char *name);
+
+ static void fillDefaultNetworkInterface(PJ_CONTEXT *ctx);
+
+ static void clearCache();
+};
+
+// ---------------------------------------------------------------------------
+
+class File {
+ protected:
+ std::string name_;
+ explicit File(const std::string &name);
+
+ public:
+ virtual ~File();
+ virtual size_t read(void *buffer, size_t sizeBytes) = 0;
+ virtual bool seek(unsigned long long offset, int whence = SEEK_SET) = 0;
+ virtual unsigned long long tell() = 0;
+ virtual void reassign_context(PJ_CONTEXT *ctx) = 0;
+
+ const std::string &name() const { return name_; }
+};
+
+//! @endcond Doxygen_Suppress
+
+NS_PROJ_END
+
+#endif // FILEMANAGER_HPP_INCLUDED \ No newline at end of file
diff --git a/src/grids.cpp b/src/grids.cpp
index 7d19b1f7..5a99106b 100644
--- a/src/grids.cpp
+++ b/src/grids.cpp
@@ -29,11 +29,21 @@
#ifndef FROM_PROJ_CPP
#define FROM_PROJ_CPP
#endif
+#define LRU11_DO_NOT_DEFINE_OUT_OF_CLASS_METHODS
#include "grids.hpp"
+#include "filemanager.hpp"
#include "proj/internal/internal.hpp"
+#include "proj/internal/lru_cache.hpp"
#include "proj_internal.h"
+#ifdef TIFF_ENABLED
+#include "tiffio.h"
+#endif
+
+#include <algorithm>
+#include <cmath>
+
NS_PROJ_START
using namespace internal;
@@ -64,14 +74,31 @@ static void swap_words(void *dataIn, size_t word_size, size_t word_count)
}
}
+// ---------------------------------------------------------------------------
+
bool ExtentAndRes::fullWorldLongitude() const {
return eastLon - westLon + resLon >= 2 * M_PI - 1e-10;
}
// ---------------------------------------------------------------------------
-Grid::Grid(int widthIn, int heightIn, const ExtentAndRes &extentIn)
- : m_width(widthIn), m_height(heightIn), m_extent(extentIn) {}
+bool ExtentAndRes::contains(const ExtentAndRes &other) const {
+ return other.westLon >= westLon && other.eastLon <= eastLon &&
+ other.southLat >= southLat && other.northLat <= northLat;
+}
+
+// ---------------------------------------------------------------------------
+
+bool ExtentAndRes::intersects(const ExtentAndRes &other) const {
+ return other.westLon < eastLon && westLon <= other.westLon &&
+ other.southLat < northLat && southLat <= other.northLat;
+}
+
+// ---------------------------------------------------------------------------
+
+Grid::Grid(const std::string &name, int widthIn, int heightIn,
+ const ExtentAndRes &extentIn)
+ : m_name(name), m_width(widthIn), m_height(heightIn), m_extent(extentIn) {}
// ---------------------------------------------------------------------------
@@ -79,9 +106,9 @@ Grid::~Grid() = default;
// ---------------------------------------------------------------------------
-VerticalShiftGrid::VerticalShiftGrid(int widthIn, int heightIn,
- const ExtentAndRes &extentIn)
- : Grid(widthIn, heightIn, extentIn) {}
+VerticalShiftGrid::VerticalShiftGrid(const std::string &nameIn, int widthIn,
+ int heightIn, const ExtentAndRes &extentIn)
+ : Grid(nameIn, widthIn, heightIn, extentIn) {}
// ---------------------------------------------------------------------------
@@ -107,11 +134,12 @@ static ExtentAndRes globalExtent() {
class NullVerticalShiftGrid : public VerticalShiftGrid {
public:
- NullVerticalShiftGrid() : VerticalShiftGrid(3, 3, globalExtent()) {}
+ NullVerticalShiftGrid() : VerticalShiftGrid("null", 3, 3, globalExtent()) {}
bool isNullGrid() const override { return true; }
bool valueAt(int, int, float &out) const override;
bool isNodata(float, double) const override { return false; }
+ void reassign_context(PJ_CONTEXT *) override {}
};
// ---------------------------------------------------------------------------
@@ -125,38 +153,47 @@ bool NullVerticalShiftGrid::valueAt(int, int, float &out) const {
class GTXVerticalShiftGrid : public VerticalShiftGrid {
PJ_CONTEXT *m_ctx;
- PAFile m_fp;
+ std::unique_ptr<File> m_fp;
GTXVerticalShiftGrid(const GTXVerticalShiftGrid &) = delete;
GTXVerticalShiftGrid &operator=(const GTXVerticalShiftGrid &) = delete;
public:
- GTXVerticalShiftGrid(PJ_CONTEXT *ctx, PAFile fp, int widthIn, int heightIn,
- const ExtentAndRes &extentIn)
- : VerticalShiftGrid(widthIn, heightIn, extentIn), m_ctx(ctx), m_fp(fp) {
- }
+ explicit GTXVerticalShiftGrid(PJ_CONTEXT *ctx, std::unique_ptr<File> &&fp,
+ const std::string &nameIn, int widthIn,
+ int heightIn, const ExtentAndRes &extentIn)
+ : VerticalShiftGrid(nameIn, widthIn, heightIn, extentIn), m_ctx(ctx),
+ m_fp(std::move(fp)) {}
~GTXVerticalShiftGrid() override;
bool valueAt(int x, int y, float &out) const override;
bool isNodata(float val, double multiplier) const override;
- static GTXVerticalShiftGrid *open(PJ_CONTEXT *ctx, PAFile fp);
+ static GTXVerticalShiftGrid *open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
+ const std::string &name);
+
+ void reassign_context(PJ_CONTEXT *ctx) override {
+ m_ctx = ctx;
+ m_fp->reassign_context(ctx);
+ }
};
// ---------------------------------------------------------------------------
-GTXVerticalShiftGrid::~GTXVerticalShiftGrid() { pj_ctx_fclose(m_ctx, m_fp); }
+GTXVerticalShiftGrid::~GTXVerticalShiftGrid() = default;
// ---------------------------------------------------------------------------
-GTXVerticalShiftGrid *GTXVerticalShiftGrid::open(PJ_CONTEXT *ctx, PAFile fp) {
+GTXVerticalShiftGrid *GTXVerticalShiftGrid::open(PJ_CONTEXT *ctx,
+ std::unique_ptr<File> fp,
+ const std::string &name) {
unsigned char header[40];
/* -------------------------------------------------------------------- */
/* Read the header. */
/* -------------------------------------------------------------------- */
- if (pj_ctx_fread(ctx, header, sizeof(header), 1, fp) != 1) {
+ if (fp->read(header, sizeof(header)) != sizeof(header)) {
pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
return nullptr;
}
@@ -206,7 +243,8 @@ GTXVerticalShiftGrid *GTXVerticalShiftGrid::open(PJ_CONTEXT *ctx, PAFile fp) {
extent.eastLon = (xorigin + xstep * (columns - 1)) * DEG_TO_RAD;
extent.northLat = (yorigin + ystep * (rows - 1)) * DEG_TO_RAD;
- return new GTXVerticalShiftGrid(ctx, fp, columns, rows, extent);
+ return new GTXVerticalShiftGrid(ctx, std::move(fp), name, columns, rows,
+ extent);
}
// ---------------------------------------------------------------------------
@@ -214,8 +252,8 @@ GTXVerticalShiftGrid *GTXVerticalShiftGrid::open(PJ_CONTEXT *ctx, PAFile fp) {
bool GTXVerticalShiftGrid::valueAt(int x, int y, float &out) const {
assert(x >= 0 && y >= 0 && x < m_width && y < m_height);
- pj_ctx_fseek(m_ctx, m_fp, 40 + sizeof(float) * (y * m_width + x), SEEK_SET);
- if (pj_ctx_fread(m_ctx, &out, sizeof(out), 1, m_fp) != 1) {
+ m_fp->seek(40 + sizeof(float) * (y * m_width + x));
+ if (m_fp->read(&out, sizeof(out)) != sizeof(out)) {
pj_ctx_set_errno(m_ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
return false;
}
@@ -246,6 +284,1026 @@ VerticalShiftGridSet::~VerticalShiftGridSet() = default;
// ---------------------------------------------------------------------------
+static bool IsTIFF(size_t header_size, const unsigned char *header) {
+ // Test combinations of signature for ClassicTIFF/BigTIFF little/big endian
+ return header_size >= 4 && (((header[0] == 'I' && header[1] == 'I') ||
+ (header[0] == 'M' && header[1] == 'M')) &&
+ ((header[2] == 0x2A && header[3] == 0) ||
+ (header[3] == 0x2A && header[2] == 0) ||
+ (header[2] == 0x2B && header[3] == 0) ||
+ (header[3] == 0x2B && header[2] == 0)));
+}
+
+#ifdef TIFF_ENABLED
+
+// ---------------------------------------------------------------------------
+
+enum class TIFFDataType { Int16, UInt16, Int32, UInt32, Float32, Float64 };
+
+// ---------------------------------------------------------------------------
+
+constexpr uint16 TIFFTAG_GEOPIXELSCALE = 33550;
+constexpr uint16 TIFFTAG_GEOTIEPOINTS = 33922;
+constexpr uint16 TIFFTAG_GEOTRANSMATRIX = 34264;
+constexpr uint16 TIFFTAG_GEOKEYDIRECTORY = 34735;
+constexpr uint16 TIFFTAG_GEODOUBLEPARAMS = 34736;
+constexpr uint16 TIFFTAG_GEOASCIIPARAMS = 34737;
+constexpr uint16 TIFFTAG_GDAL_METADATA = 42112;
+constexpr uint16 TIFFTAG_GDAL_NODATA = 42113;
+
+// ---------------------------------------------------------------------------
+
+class BlockCache {
+ public:
+ void insert(uint32 ifdIdx, uint32 blockNumber,
+ const std::vector<unsigned char> &data);
+ std::shared_ptr<std::vector<unsigned char>> get(uint32 ifdIdx,
+ uint32 blockNumber);
+
+ private:
+ struct Key {
+ uint32 ifdIdx;
+ uint32 blockNumber;
+
+ Key(uint32 ifdIdxIn, uint32 blockNumberIn)
+ : ifdIdx(ifdIdxIn), blockNumber(blockNumberIn) {}
+ bool operator==(const Key &other) const {
+ return ifdIdx == other.ifdIdx && blockNumber == other.blockNumber;
+ }
+ };
+
+ struct KeyHasher {
+ std::size_t operator()(const Key &k) const {
+ return k.ifdIdx ^ (k.blockNumber << 16) ^ (k.blockNumber >> 16);
+ }
+ };
+
+ static constexpr int NUM_BLOCKS_AT_CROSSING_TILES = 4;
+ static constexpr int MAX_SAMPLE_COUNT = 3;
+ lru11::Cache<
+ Key, std::shared_ptr<std::vector<unsigned char>>, lru11::NullLock,
+ std::unordered_map<
+ Key,
+ typename std::list<lru11::KeyValuePair<
+ Key, std::shared_ptr<std::vector<unsigned char>>>>::iterator,
+ KeyHasher>>
+ cache_{NUM_BLOCKS_AT_CROSSING_TILES * MAX_SAMPLE_COUNT};
+};
+
+// ---------------------------------------------------------------------------
+
+void BlockCache::insert(uint32 ifdIdx, uint32 blockNumber,
+ const std::vector<unsigned char> &data) {
+ cache_.insert(Key(ifdIdx, blockNumber),
+ std::make_shared<std::vector<unsigned char>>(data));
+}
+
+// ---------------------------------------------------------------------------
+
+std::shared_ptr<std::vector<unsigned char>>
+BlockCache::get(uint32 ifdIdx, uint32 blockNumber) {
+ std::shared_ptr<std::vector<unsigned char>> ret;
+ cache_.tryGet(Key(ifdIdx, blockNumber), ret);
+ return ret;
+}
+
+// ---------------------------------------------------------------------------
+
+class GTiffGrid : public Grid {
+ PJ_CONTEXT *m_ctx; // owned by the belonging GTiffDataset
+ TIFF *m_hTIFF; // owned by the belonging GTiffDataset
+ BlockCache &m_cache; // owned by the belonging GTiffDataset
+ uint32 m_ifdIdx;
+ TIFFDataType m_dt;
+ uint16 m_samplesPerPixel;
+ uint16 m_planarConfig;
+ bool m_bottomUp;
+ toff_t m_dirOffset;
+ bool m_tiled;
+ uint32 m_blockWidth = 0;
+ uint32 m_blockHeight = 0;
+ mutable std::vector<unsigned char> m_buffer{};
+ unsigned m_blocksPerRow = 0;
+ unsigned m_blocksPerCol = 0;
+ std::map<int, double> m_mapOffset{};
+ std::map<int, double> m_mapScale{};
+ std::map<std::pair<int, std::string>, std::string> m_metadata{};
+ bool m_hasNodata = false;
+ float m_noData = 0.0f;
+ uint32 m_subfileType = 0;
+
+ GTiffGrid(const GTiffGrid &) = delete;
+ GTiffGrid &operator=(const GTiffGrid &) = delete;
+
+ void getScaleOffset(double &scale, double &offset, uint16 sample) const;
+
+ template <class T>
+ float readValue(const std::vector<unsigned char> &buffer,
+ uint32 offsetInBlock, uint16 sample) const;
+
+ public:
+ GTiffGrid(PJ_CONTEXT *ctx, TIFF *hTIFF, BlockCache &cache, uint32 ifdIdx,
+ const std::string &nameIn, int widthIn, int heightIn,
+ const ExtentAndRes &extentIn, TIFFDataType dtIn,
+ uint16 samplesPerPixelIn, uint16 planarConfig, bool bottomUpIn);
+
+ ~GTiffGrid() override;
+
+ uint16 samplesPerPixel() const { return m_samplesPerPixel; }
+
+ bool valueAt(uint16 sample, int x, int y, float &out) const;
+
+ bool isNodata(float val) const;
+
+ std::string metadataItem(const std::string &key, int sample = -1) const;
+
+ uint32 subfileType() const { return m_subfileType; }
+
+ void reassign_context(PJ_CONTEXT *ctx) { m_ctx = ctx; }
+};
+
+// ---------------------------------------------------------------------------
+
+GTiffGrid::GTiffGrid(PJ_CONTEXT *ctx, TIFF *hTIFF, BlockCache &cache,
+ uint32 ifdIdx, const std::string &nameIn, int widthIn,
+ int heightIn, const ExtentAndRes &extentIn,
+ TIFFDataType dtIn, uint16 samplesPerPixelIn,
+ uint16 planarConfig, bool bottomUpIn)
+ : Grid(nameIn, widthIn, heightIn, extentIn), m_ctx(ctx), m_hTIFF(hTIFF),
+ m_cache(cache), m_ifdIdx(ifdIdx), m_dt(dtIn),
+ m_samplesPerPixel(samplesPerPixelIn), m_planarConfig(planarConfig),
+ m_bottomUp(bottomUpIn), m_dirOffset(TIFFCurrentDirOffset(hTIFF)),
+ m_tiled(TIFFIsTiled(hTIFF) != 0) {
+
+ if (m_tiled) {
+ TIFFGetField(m_hTIFF, TIFFTAG_TILEWIDTH, &m_blockWidth);
+ TIFFGetField(m_hTIFF, TIFFTAG_TILELENGTH, &m_blockHeight);
+ } else {
+ m_blockWidth = widthIn;
+ TIFFGetField(m_hTIFF, TIFFTAG_ROWSPERSTRIP, &m_blockHeight);
+ if (m_blockHeight > static_cast<unsigned>(m_height))
+ m_blockHeight = m_height;
+ }
+
+ TIFFGetField(m_hTIFF, TIFFTAG_SUBFILETYPE, &m_subfileType);
+
+ m_blocksPerRow = (m_width + m_blockWidth - 1) / m_blockWidth;
+ m_blocksPerCol = (m_height + m_blockHeight - 1) / m_blockHeight;
+
+ const char *text = nullptr;
+ // Poor-man XML parsing of TIFFTAG_GDAL_METADATA tag. Hopefully good
+ // enough for our purposes.
+ if (TIFFGetField(m_hTIFF, TIFFTAG_GDAL_METADATA, &text)) {
+ const char *ptr = text;
+ while (true) {
+ ptr = strstr(ptr, "<Item ");
+ if (ptr == nullptr)
+ break;
+ const char *endTag = strchr(ptr, '>');
+ if (endTag == nullptr)
+ break;
+ const char *endValue = strchr(endTag, '<');
+ if (endValue == nullptr)
+ break;
+
+ std::string tag;
+ tag.append(ptr, endTag - ptr);
+
+ std::string value;
+ value.append(endTag + 1, endValue - (endTag + 1));
+
+ std::string name;
+ auto namePos = tag.find("name=\"");
+ if (namePos == std::string::npos)
+ break;
+ {
+ namePos += strlen("name=\"");
+ const auto endQuote = tag.find('"', namePos);
+ if (endQuote == std::string::npos)
+ break;
+ name = tag.substr(namePos, endQuote - namePos);
+ }
+
+ const auto samplePos = tag.find("sample=\"");
+ int sample = -1;
+ if (samplePos != std::string::npos) {
+ sample = atoi(tag.c_str() + samplePos + strlen("sample=\""));
+ }
+
+ m_metadata[std::pair<int, std::string>(sample, name)] = value;
+
+ auto rolePos = tag.find("role=\"");
+ if (rolePos != std::string::npos) {
+ rolePos += strlen("role=\"");
+ const auto endQuote = tag.find('"', rolePos);
+ if (endQuote == std::string::npos)
+ break;
+ const auto role = tag.substr(rolePos, endQuote - rolePos);
+ if (role == "offset") {
+ if (sample >= 0) {
+ try {
+ m_mapOffset[sample] = c_locale_stod(value);
+ } catch (const std::exception &) {
+ }
+ }
+ } else if (role == "scale") {
+ if (sample >= 0) {
+ try {
+ m_mapScale[sample] = c_locale_stod(value);
+ } catch (const std::exception &) {
+ }
+ }
+ }
+ }
+
+ ptr = endValue + 1;
+ }
+ }
+
+ if (TIFFGetField(m_hTIFF, TIFFTAG_GDAL_NODATA, &text)) {
+ try {
+ m_noData = static_cast<float>(c_locale_stod(text));
+ m_hasNodata = true;
+ } catch (const std::exception &) {
+ }
+ }
+
+ auto oIter = m_metadata.find(std::pair<int, std::string>(-1, "grid_name"));
+ if (oIter != m_metadata.end()) {
+ m_name += ", " + oIter->second;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+GTiffGrid::~GTiffGrid() = default;
+
+// ---------------------------------------------------------------------------
+
+void GTiffGrid::getScaleOffset(double &scale, double &offset,
+ uint16 sample) const {
+ {
+ auto iter = m_mapScale.find(sample);
+ if (iter != m_mapScale.end())
+ scale = iter->second;
+ }
+
+ {
+ auto iter = m_mapOffset.find(sample);
+ if (iter != m_mapOffset.end())
+ offset = iter->second;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+template <class T>
+float GTiffGrid::readValue(const std::vector<unsigned char> &buffer,
+ uint32 offsetInBlock, uint16 sample) const {
+ const auto ptr = reinterpret_cast<const T *>(buffer.data());
+ assert(offsetInBlock < buffer.size() / sizeof(T));
+ const auto val = ptr[offsetInBlock];
+ if (!m_hasNodata || static_cast<float>(val) != m_noData) {
+ double scale = 1;
+ double offset = 0;
+ getScaleOffset(scale, offset, sample);
+ return static_cast<float>(val * scale + offset);
+ } else {
+ return static_cast<float>(val);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+bool GTiffGrid::valueAt(uint16 sample, int x, int yFromBottom,
+ float &out) const {
+ assert(x >= 0 && yFromBottom >= 0 && x < m_width && yFromBottom < m_height);
+ assert(sample < m_samplesPerPixel);
+
+ const int blockX = x / m_blockWidth;
+
+ // All non-TIFF grids have the first rows in the file being the one
+ // corresponding to the southern-most row. In GeoTIFF, the convention is
+ // *generally* different (when m_bottomUp == false), TIFF being an
+ // image-oriented image. If m_bottomUp == true, then we had GeoTIFF hints
+ // that the first row of the image is the southern-most.
+ const int yTIFF = m_bottomUp ? yFromBottom : m_height - 1 - yFromBottom;
+ const int blockY = yTIFF / m_blockHeight;
+
+ uint32 blockId = blockY * m_blocksPerRow + blockX;
+ if (m_planarConfig == PLANARCONFIG_SEPARATE) {
+ blockId += sample * m_blocksPerCol * m_blocksPerRow;
+ }
+
+ auto cachedBuffer = m_cache.get(m_ifdIdx, blockId);
+ std::vector<unsigned char> *pBuffer = &m_buffer;
+ if (cachedBuffer != nullptr) {
+ // Safe as we don't access the cache before pBuffer is used
+ pBuffer = cachedBuffer.get();
+ } else {
+ if (TIFFCurrentDirOffset(m_hTIFF) != m_dirOffset &&
+ !TIFFSetSubDirectory(m_hTIFF, m_dirOffset)) {
+ return false;
+ }
+ if (m_buffer.empty()) {
+ const auto blockSize = static_cast<size_t>(
+ m_tiled ? TIFFTileSize64(m_hTIFF) : TIFFStripSize64(m_hTIFF));
+ try {
+ m_buffer.resize(blockSize);
+ } catch (const std::exception &e) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Exception %s", e.what());
+ return false;
+ }
+ }
+
+ if (m_tiled) {
+ if (TIFFReadEncodedTile(m_hTIFF, blockId, m_buffer.data(),
+ m_buffer.size()) == -1) {
+ return false;
+ }
+ } else {
+ if (TIFFReadEncodedStrip(m_hTIFF, blockId, m_buffer.data(),
+ m_buffer.size()) == -1) {
+ return false;
+ }
+ }
+
+ try {
+ m_cache.insert(m_ifdIdx, blockId, m_buffer);
+ } catch (const std::exception &e) {
+ // Should normally not happen
+ pj_log(m_ctx, PJ_LOG_ERROR, "Exception %s", e.what());
+ }
+ }
+
+ uint32 offsetInBlock =
+ (x % m_blockWidth) + (yTIFF % m_blockHeight) * m_blockWidth;
+ if (m_planarConfig == PLANARCONFIG_CONTIG)
+ offsetInBlock = offsetInBlock * m_samplesPerPixel + sample;
+
+ switch (m_dt) {
+ case TIFFDataType::Int16:
+ out = readValue<short>(*pBuffer, offsetInBlock, sample);
+ break;
+
+ case TIFFDataType::UInt16:
+ out = readValue<unsigned short>(*pBuffer, offsetInBlock, sample);
+ break;
+
+ case TIFFDataType::Int32:
+ out = readValue<int>(*pBuffer, offsetInBlock, sample);
+ break;
+
+ case TIFFDataType::UInt32:
+ out = readValue<unsigned int>(*pBuffer, offsetInBlock, sample);
+ break;
+
+ case TIFFDataType::Float32:
+ out = readValue<float>(*pBuffer, offsetInBlock, sample);
+ break;
+
+ case TIFFDataType::Float64:
+ out = readValue<double>(*pBuffer, offsetInBlock, sample);
+ break;
+ }
+
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+bool GTiffGrid::isNodata(float val) const {
+ return (m_hasNodata && val == m_noData) || std::isnan(val);
+}
+
+// ---------------------------------------------------------------------------
+
+std::string GTiffGrid::metadataItem(const std::string &key, int sample) const {
+ auto iter = m_metadata.find(std::pair<int, std::string>(sample, key));
+ if (iter == m_metadata.end()) {
+ return std::string();
+ }
+ return iter->second;
+}
+
+// ---------------------------------------------------------------------------
+
+class GTiffDataset {
+ PJ_CONTEXT *m_ctx;
+ std::unique_ptr<File> m_fp;
+ TIFF *m_hTIFF = nullptr;
+ bool m_hasNextGrid = false;
+ uint32 m_ifdIdx = 0;
+ toff_t m_nextDirOffset = 0;
+ std::string m_filename{};
+ BlockCache m_cache{};
+
+ GTiffDataset(const GTiffDataset &) = delete;
+ GTiffDataset &operator=(const GTiffDataset &) = delete;
+
+ // libtiff I/O routines
+ static tsize_t tiffReadProc(thandle_t fd, tdata_t buf, tsize_t size) {
+ GTiffDataset *self = static_cast<GTiffDataset *>(fd);
+ return self->m_fp->read(buf, size);
+ }
+
+ static tsize_t tiffWriteProc(thandle_t, tdata_t, tsize_t) {
+ assert(false);
+ return 0;
+ }
+
+ static toff_t tiffSeekProc(thandle_t fd, toff_t off, int whence) {
+ GTiffDataset *self = static_cast<GTiffDataset *>(fd);
+ if (self->m_fp->seek(off, whence))
+ return static_cast<toff_t>(self->m_fp->tell());
+ else
+ return static_cast<toff_t>(-1);
+ }
+
+ static int tiffCloseProc(thandle_t) {
+ // done in destructor
+ return 0;
+ }
+
+ static toff_t tiffSizeProc(thandle_t fd) {
+ GTiffDataset *self = static_cast<GTiffDataset *>(fd);
+ const auto old_off = self->m_fp->tell();
+ self->m_fp->seek(0, SEEK_END);
+ const auto file_size = static_cast<toff_t>(self->m_fp->tell());
+ self->m_fp->seek(old_off);
+ return file_size;
+ }
+
+ static int tiffMapProc(thandle_t, tdata_t *, toff_t *) { return (0); }
+
+ static void tiffUnmapProc(thandle_t, tdata_t, toff_t) {}
+
+ public:
+ GTiffDataset(PJ_CONTEXT *ctx, std::unique_ptr<File> &&fp)
+ : m_ctx(ctx), m_fp(std::move(fp)) {}
+ virtual ~GTiffDataset();
+
+ bool openTIFF(const std::string &filename);
+
+ std::unique_ptr<GTiffGrid> nextGrid();
+
+ void reassign_context(PJ_CONTEXT *ctx) {
+ m_ctx = ctx;
+ m_fp->reassign_context(ctx);
+ }
+};
+
+// ---------------------------------------------------------------------------
+
+GTiffDataset::~GTiffDataset() {
+ if (m_hTIFF)
+ TIFFClose(m_hTIFF);
+}
+
+// ---------------------------------------------------------------------------
+class OneTimeTIFFTagInit {
+
+ static TIFFExtendProc ParentExtender;
+
+ // Function called by libtiff when initializing a TIFF directory
+ static void GTiffTagExtender(TIFF *tif) {
+ static const TIFFFieldInfo xtiffFieldInfo[] = {
+ // GeoTIFF tags
+ {TIFFTAG_GEOPIXELSCALE, -1, -1, TIFF_DOUBLE, FIELD_CUSTOM, TRUE,
+ TRUE, const_cast<char *>("GeoPixelScale")},
+ {TIFFTAG_GEOTIEPOINTS, -1, -1, TIFF_DOUBLE, FIELD_CUSTOM, TRUE,
+ TRUE, const_cast<char *>("GeoTiePoints")},
+ {TIFFTAG_GEOTRANSMATRIX, -1, -1, TIFF_DOUBLE, FIELD_CUSTOM, TRUE,
+ TRUE, const_cast<char *>("GeoTransformationMatrix")},
+
+ {TIFFTAG_GEOKEYDIRECTORY, -1, -1, TIFF_SHORT, FIELD_CUSTOM, TRUE,
+ TRUE, const_cast<char *>("GeoKeyDirectory")},
+ {TIFFTAG_GEODOUBLEPARAMS, -1, -1, TIFF_DOUBLE, FIELD_CUSTOM, TRUE,
+ TRUE, const_cast<char *>("GeoDoubleParams")},
+ {TIFFTAG_GEOASCIIPARAMS, -1, -1, TIFF_ASCII, FIELD_CUSTOM, TRUE,
+ FALSE, const_cast<char *>("GeoASCIIParams")},
+
+ // GDAL tags
+ {TIFFTAG_GDAL_METADATA, -1, -1, TIFF_ASCII, FIELD_CUSTOM, TRUE,
+ FALSE, const_cast<char *>("GDALMetadata")},
+ {TIFFTAG_GDAL_NODATA, -1, -1, TIFF_ASCII, FIELD_CUSTOM, TRUE, FALSE,
+ const_cast<char *>("GDALNoDataValue")},
+
+ };
+
+ if (ParentExtender)
+ (*ParentExtender)(tif);
+
+ TIFFMergeFieldInfo(tif, xtiffFieldInfo,
+ sizeof(xtiffFieldInfo) / sizeof(xtiffFieldInfo[0]));
+ }
+
+ public:
+ OneTimeTIFFTagInit() {
+ assert(ParentExtender == nullptr);
+ // Install our TIFF tag extender
+ ParentExtender = TIFFSetTagExtender(GTiffTagExtender);
+ }
+};
+
+TIFFExtendProc OneTimeTIFFTagInit::ParentExtender = nullptr;
+
+// ---------------------------------------------------------------------------
+
+bool GTiffDataset::openTIFF(const std::string &filename) {
+ static OneTimeTIFFTagInit oneTimeTIFFTagInit;
+ m_hTIFF =
+ TIFFClientOpen(filename.c_str(), "r", static_cast<thandle_t>(this),
+ GTiffDataset::tiffReadProc, GTiffDataset::tiffWriteProc,
+ GTiffDataset::tiffSeekProc, GTiffDataset::tiffCloseProc,
+ GTiffDataset::tiffSizeProc, GTiffDataset::tiffMapProc,
+ GTiffDataset::tiffUnmapProc);
+
+ m_filename = filename;
+ m_hasNextGrid = true;
+ return m_hTIFF != nullptr;
+}
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<GTiffGrid> GTiffDataset::nextGrid() {
+ if (!m_hasNextGrid)
+ return nullptr;
+ if (m_nextDirOffset) {
+ TIFFSetSubDirectory(m_hTIFF, m_nextDirOffset);
+ }
+
+ uint32 width = 0;
+ uint32 height = 0;
+ TIFFGetField(m_hTIFF, TIFFTAG_IMAGEWIDTH, &width);
+ TIFFGetField(m_hTIFF, TIFFTAG_IMAGELENGTH, &height);
+ if (width == 0 || height == 0 || width > INT_MAX || height > INT_MAX) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Invalid image size");
+ return nullptr;
+ }
+
+ uint16 samplesPerPixel = 0;
+ if (!TIFFGetField(m_hTIFF, TIFFTAG_SAMPLESPERPIXEL, &samplesPerPixel)) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Missing SamplesPerPixel tag");
+ return nullptr;
+ }
+ if (samplesPerPixel == 0) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Invalid SamplesPerPixel value");
+ return nullptr;
+ }
+
+ uint16 bitsPerSample = 0;
+ if (!TIFFGetField(m_hTIFF, TIFFTAG_BITSPERSAMPLE, &bitsPerSample)) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Missing BitsPerSample tag");
+ return nullptr;
+ }
+
+ uint16 planarConfig = 0;
+ if (!TIFFGetField(m_hTIFF, TIFFTAG_PLANARCONFIG, &planarConfig)) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Missing PlanarConfig tag");
+ return nullptr;
+ }
+
+ uint16 sampleFormat = 0;
+ if (!TIFFGetField(m_hTIFF, TIFFTAG_SAMPLEFORMAT, &sampleFormat)) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Missing SampleFormat tag");
+ return nullptr;
+ }
+
+ TIFFDataType dt;
+ if (sampleFormat == SAMPLEFORMAT_INT && bitsPerSample == 16)
+ dt = TIFFDataType::Int16;
+ else if (sampleFormat == SAMPLEFORMAT_UINT && bitsPerSample == 16)
+ dt = TIFFDataType::UInt16;
+ else if (sampleFormat == SAMPLEFORMAT_INT && bitsPerSample == 32)
+ dt = TIFFDataType::Int32;
+ else if (sampleFormat == SAMPLEFORMAT_UINT && bitsPerSample == 32)
+ dt = TIFFDataType::UInt32;
+ else if (sampleFormat == SAMPLEFORMAT_IEEEFP && bitsPerSample == 32)
+ dt = TIFFDataType::Float32;
+ else if (sampleFormat == SAMPLEFORMAT_IEEEFP && bitsPerSample == 64)
+ dt = TIFFDataType::Float64;
+ else {
+ pj_log(
+ m_ctx, PJ_LOG_ERROR,
+ "Unsupported combination of SampleFormat and BitsPerSample values");
+ return nullptr;
+ }
+
+ uint16 photometric = PHOTOMETRIC_MINISBLACK;
+ if (!TIFFGetField(m_hTIFF, TIFFTAG_PHOTOMETRIC, &photometric))
+ photometric = PHOTOMETRIC_MINISBLACK;
+ if (photometric != PHOTOMETRIC_MINISBLACK) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Unsupported Photometric value");
+ return nullptr;
+ }
+
+ uint16 compression = COMPRESSION_NONE;
+ if (!TIFFGetField(m_hTIFF, TIFFTAG_COMPRESSION, &compression))
+ compression = COMPRESSION_NONE;
+
+ if (compression != COMPRESSION_NONE &&
+ !TIFFIsCODECConfigured(compression)) {
+ pj_log(m_ctx, PJ_LOG_ERROR,
+ "Cannot open TIFF file due to missing codec.");
+ return nullptr;
+ }
+ // We really don't want to try dealing with old-JPEG images
+ if (compression == COMPRESSION_OJPEG) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Unsupported compression method.");
+ return nullptr;
+ }
+
+ const auto blockSize = TIFFIsTiled(m_hTIFF) ? TIFFTileSize64(m_hTIFF)
+ : TIFFStripSize64(m_hTIFF);
+ if (blockSize == 0 || blockSize > 64 * 1024 * 2014) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Unsupported block size.");
+ return nullptr;
+ }
+
+ unsigned short count = 0;
+ unsigned short *geokeys = nullptr;
+ bool pixelIsArea = false;
+ if (!TIFFGetField(m_hTIFF, TIFFTAG_GEOKEYDIRECTORY, &count, &geokeys)) {
+ pj_log(m_ctx, PJ_LOG_DEBUG_MINOR, "No GeoKeys tag");
+ } else {
+ if (count < 4 || (count % 4) != 0) {
+ pj_log(m_ctx, PJ_LOG_ERROR,
+ "Wrong number of values in GeoKeys tag");
+ return nullptr;
+ }
+
+ if (geokeys[0] != 1) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Unsupported GeoTIFF major version");
+ return nullptr;
+ }
+ // We only know that we support GeoTIFF 1.0 and 1.1 at that time
+ if (geokeys[1] != 1 || geokeys[2] > 1) {
+ pj_log(m_ctx, PJ_LOG_DEBUG_MINOR,
+ "GeoTIFF %d.%d possibly not handled", geokeys[1],
+ geokeys[2]);
+ }
+
+ for (unsigned int i = 4; i + 3 < count; i += 4) {
+ constexpr unsigned short GTModelTypeGeoKey = 1024;
+ constexpr unsigned short ModelTypeGeographic = 2;
+
+ constexpr unsigned short GTRasterTypeGeoKey = 1025;
+ constexpr unsigned short RasterPixelIsArea = 1;
+ // constexpr unsigned short RasterPixelIsPoint = 2;
+
+ if (geokeys[i] == GTModelTypeGeoKey) {
+ if (geokeys[i + 3] != ModelTypeGeographic) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Only GTModelTypeGeoKey = "
+ "ModelTypeGeographic is "
+ "supported");
+ return nullptr;
+ }
+ } else if (geokeys[i] == GTRasterTypeGeoKey) {
+ if (geokeys[i + 3] == RasterPixelIsArea) {
+ pixelIsArea = true;
+ }
+ }
+ }
+ }
+
+ double hRes = 0;
+ double vRes = 0;
+ double westLon = 0;
+ double northLat = 0;
+
+ double *matrix = nullptr;
+ if (TIFFGetField(m_hTIFF, TIFFTAG_GEOTRANSMATRIX, &count, &matrix) &&
+ count == 16) {
+ // If using GDAL to produce a bottom-up georeferencing, it will produce
+ // a GeoTransformationMatrix, since negative values in GeoPixelScale
+ // have historically been implementation bugs.
+ if (matrix[1] != 0 || matrix[4] != 0) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Rotational terms not supported in "
+ "GeoTransformationMatrix tag");
+ return nullptr;
+ }
+
+ westLon = matrix[3];
+ hRes = matrix[0];
+ northLat = matrix[7];
+ vRes = -matrix[5]; // negation to simulate GeoPixelScale convention
+ } else {
+ double *geopixelscale = nullptr;
+ if (TIFFGetField(m_hTIFF, TIFFTAG_GEOPIXELSCALE, &count,
+ &geopixelscale) != 1) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "No GeoPixelScale tag");
+ return nullptr;
+ }
+ if (count != 3) {
+ pj_log(m_ctx, PJ_LOG_ERROR,
+ "Wrong number of values in GeoPixelScale tag");
+ return nullptr;
+ }
+ hRes = geopixelscale[0];
+ vRes = geopixelscale[1];
+
+ double *geotiepoints = nullptr;
+ if (TIFFGetField(m_hTIFF, TIFFTAG_GEOTIEPOINTS, &count,
+ &geotiepoints) != 1) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "No GeoTiePoints tag");
+ return nullptr;
+ }
+ if (count != 6) {
+ pj_log(m_ctx, PJ_LOG_ERROR,
+ "Wrong number of values in GeoTiePoints tag");
+ return nullptr;
+ }
+
+ westLon = geotiepoints[3] - geotiepoints[0] * hRes;
+ northLat = geotiepoints[4] + geotiepoints[1] * vRes;
+ }
+
+ if (pixelIsArea) {
+ westLon += 0.5 * hRes;
+ northLat -= 0.5 * vRes;
+ }
+
+ ExtentAndRes extent;
+ extent.westLon = westLon * DEG_TO_RAD;
+ extent.northLat = northLat * DEG_TO_RAD;
+ extent.resLon = hRes * DEG_TO_RAD;
+ extent.resLat = fabs(vRes) * DEG_TO_RAD;
+ extent.eastLon = (westLon + hRes * (width - 1)) * DEG_TO_RAD;
+ extent.southLat = (northLat - vRes * (height - 1)) * DEG_TO_RAD;
+
+ if (vRes < 0) {
+ std::swap(extent.northLat, extent.southLat);
+ }
+
+ if (!(fabs(extent.westLon) <= 4 * M_PI &&
+ fabs(extent.eastLon) <= 4 * M_PI &&
+ fabs(extent.northLat) <= M_PI + 1e-5 &&
+ fabs(extent.southLat) <= M_PI + 1e-5 &&
+ extent.westLon < extent.eastLon &&
+ extent.southLat < extent.northLat && extent.resLon > 1e-10 &&
+ extent.resLat > 1e-10)) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Inconsistent georeferencing for %s",
+ m_filename.c_str());
+ return nullptr;
+ }
+
+ auto ret = std::unique_ptr<GTiffGrid>(new GTiffGrid(
+ m_ctx, m_hTIFF, m_cache, m_ifdIdx, m_filename, width, height, extent,
+ dt, samplesPerPixel, planarConfig, vRes < 0));
+ m_ifdIdx++;
+ m_hasNextGrid = TIFFReadDirectory(m_hTIFF) != 0;
+ m_nextDirOffset = TIFFCurrentDirOffset(m_hTIFF);
+ return ret;
+}
+
+// ---------------------------------------------------------------------------
+
+class GTiffVGridShiftSet : public VerticalShiftGridSet, public GTiffDataset {
+
+ GTiffVGridShiftSet(PJ_CONTEXT *ctx, std::unique_ptr<File> &&fp)
+ : GTiffDataset(ctx, std::move(fp)) {}
+
+ public:
+ ~GTiffVGridShiftSet() override;
+
+ static std::unique_ptr<GTiffVGridShiftSet>
+ open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
+ const std::string &filename);
+
+ void reassign_context(PJ_CONTEXT *ctx) override {
+ VerticalShiftGridSet::reassign_context(ctx);
+ GTiffDataset::reassign_context(ctx);
+ }
+};
+
+#endif // TIFF_ENABLED
+
+// ---------------------------------------------------------------------------
+
+template <class GridType, class GenericGridType>
+static void
+insertIntoHierarchy(PJ_CONTEXT *ctx, std::unique_ptr<GridType> &&grid,
+ const std::string &gridName, const std::string &parentName,
+ std::vector<std::unique_ptr<GenericGridType>> &topGrids,
+ std::map<std::string, GridType *> &mapGrids) {
+ const auto &extent = grid->extentAndRes();
+
+ // If we have one or both of grid_name and parent_grid_name, try to use
+ // the names to recreate the hiearchy
+ if (!gridName.empty()) {
+ if (mapGrids.find(gridName) != mapGrids.end()) {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Several grids called %s found!",
+ gridName.c_str());
+ }
+ mapGrids[gridName] = grid.get();
+ }
+ bool gridInserted = false;
+ if (!parentName.empty()) {
+ auto iter = mapGrids.find(parentName);
+ if (iter == mapGrids.end()) {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "Grid %s refers to non-existing parent %s. "
+ "Using bounding-box method.",
+ gridName.c_str(), parentName.c_str());
+ } else {
+ if (iter->second->extentAndRes().contains(extent)) {
+ iter->second->m_children.emplace_back(std::move(grid));
+ gridInserted = true;
+ } else {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "Grid %s refers to parent %s, but its extent is "
+ "not included in it. Using bounding-box method.",
+ gridName.c_str(), parentName.c_str());
+ }
+ }
+ } else if (!gridName.empty()) {
+ topGrids.emplace_back(std::move(grid));
+ gridInserted = true;
+ }
+
+ // Fallback to analyzing spatial extents
+ if (!gridInserted) {
+ for (const auto &candidateParent : topGrids) {
+ const auto &candidateParentExtent = candidateParent->extentAndRes();
+ if (candidateParentExtent.contains(extent)) {
+ static_cast<GridType *>(candidateParent.get())
+ ->insertGrid(ctx, std::move(grid));
+ gridInserted = true;
+ break;
+ } else if (candidateParentExtent.intersects(extent)) {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "Partially intersecting grids found!");
+ }
+ }
+ if (!gridInserted) {
+ topGrids.emplace_back(std::move(grid));
+ }
+ }
+}
+
+#ifdef TIFF_ENABLED
+// ---------------------------------------------------------------------------
+
+class GTiffVGrid : public VerticalShiftGrid {
+ friend void insertIntoHierarchy<GTiffVGrid, VerticalShiftGrid>(
+ PJ_CONTEXT *ctx, std::unique_ptr<GTiffVGrid> &&grid,
+ const std::string &gridName, const std::string &parentName,
+ std::vector<std::unique_ptr<VerticalShiftGrid>> &topGrids,
+ std::map<std::string, GTiffVGrid *> &mapGrids);
+
+ std::unique_ptr<GTiffGrid> m_grid;
+ uint16 m_idxSample;
+
+ public:
+ GTiffVGrid(std::unique_ptr<GTiffGrid> &&grid, uint16 idxSample);
+
+ ~GTiffVGrid() override;
+
+ bool valueAt(int x, int y, float &out) const override {
+ return m_grid->valueAt(m_idxSample, x, y, out);
+ }
+
+ bool isNodata(float val, double /* multiplier */) const override {
+ return m_grid->isNodata(val);
+ }
+
+ void insertGrid(PJ_CONTEXT *ctx, std::unique_ptr<GTiffVGrid> &&subgrid);
+
+ void reassign_context(PJ_CONTEXT *ctx) override {
+ m_grid->reassign_context(ctx);
+ }
+};
+
+// ---------------------------------------------------------------------------
+
+GTiffVGridShiftSet::~GTiffVGridShiftSet() = default;
+
+// ---------------------------------------------------------------------------
+
+GTiffVGrid::GTiffVGrid(std::unique_ptr<GTiffGrid> &&grid, uint16 idxSample)
+ : VerticalShiftGrid(grid->name(), grid->width(), grid->height(),
+ grid->extentAndRes()),
+ m_grid(std::move(grid)), m_idxSample(idxSample) {}
+
+// ---------------------------------------------------------------------------
+
+GTiffVGrid::~GTiffVGrid() = default;
+
+// ---------------------------------------------------------------------------
+
+void GTiffVGrid::insertGrid(PJ_CONTEXT *ctx,
+ std::unique_ptr<GTiffVGrid> &&subgrid) {
+ bool gridInserted = false;
+ const auto &extent = subgrid->extentAndRes();
+ for (const auto &candidateParent : m_children) {
+ const auto &candidateParentExtent = candidateParent->extentAndRes();
+ if (candidateParentExtent.contains(extent)) {
+ static_cast<GTiffVGrid *>(candidateParent.get())
+ ->insertGrid(ctx, std::move(subgrid));
+ gridInserted = true;
+ break;
+ } else if (candidateParentExtent.intersects(extent)) {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "Partially intersecting grids found!");
+ }
+ }
+ if (!gridInserted) {
+ m_children.emplace_back(std::move(subgrid));
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<GTiffVGridShiftSet>
+GTiffVGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
+ const std::string &filename) {
+ auto set = std::unique_ptr<GTiffVGridShiftSet>(
+ new GTiffVGridShiftSet(ctx, std::move(fp)));
+ set->m_name = filename;
+ set->m_format = "gtiff";
+ if (!set->openTIFF(filename)) {
+ return nullptr;
+ }
+ uint16 idxSample = 0;
+
+ std::map<std::string, GTiffVGrid *> mapGrids;
+ for (int ifd = 0;; ++ifd) {
+ auto grid = set->nextGrid();
+ if (!grid) {
+ if (ifd == 0) {
+ return nullptr;
+ }
+ break;
+ }
+
+ const auto subfileType = grid->subfileType();
+ if (subfileType != 0 && subfileType != FILETYPE_PAGE) {
+ if (ifd == 0) {
+ pj_log(ctx, PJ_LOG_ERROR, "Invalid subfileType");
+ return nullptr;
+ } else {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "Ignoring IFD %d as it has a unsupported subfileType",
+ ifd);
+ continue;
+ }
+ }
+
+ // Identify the index of the geoid_undulation/vertical_offset
+ bool foundDescriptionForAtLeastOneSample = false;
+ bool foundDescriptionForShift = false;
+ for (int i = 0; i < static_cast<int>(grid->samplesPerPixel()); ++i) {
+ const auto desc = grid->metadataItem("DESCRIPTION", i);
+ if (!desc.empty()) {
+ foundDescriptionForAtLeastOneSample = true;
+ }
+ if (desc == "geoid_undulation" || desc == "vertical_offset") {
+ idxSample = static_cast<uint16>(i);
+ foundDescriptionForShift = true;
+ }
+ }
+
+ if (foundDescriptionForAtLeastOneSample) {
+ if (!foundDescriptionForShift) {
+ if (ifd > 0) {
+ // Assuming that extra IFD without our channel of interest
+ // can be ignored
+ // One could imagine to put the accuracy values in separate
+ // IFD for example
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "Ignoring IFD %d as it has no "
+ "geoid_undulation/vertical_offset channel",
+ ifd);
+ continue;
+ } else {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "IFD 0 has channel descriptions, but no "
+ "geoid_undulation/vertical_offset channel");
+ return nullptr;
+ }
+ }
+ }
+
+ if (idxSample >= grid->samplesPerPixel()) {
+ pj_log(ctx, PJ_LOG_ERROR, "Invalid sample index");
+ return nullptr;
+ }
+
+ const std::string gridName = grid->metadataItem("grid_name");
+ const std::string parentName = grid->metadataItem("parent_grid_name");
+
+ auto vgrid =
+ internal::make_unique<GTiffVGrid>(std::move(grid), idxSample);
+
+ insertIntoHierarchy(ctx, std::move(vgrid), gridName, parentName,
+ set->m_grids, mapGrids);
+ }
+ return set;
+}
+#endif // TIFF_ENABLED
+
+// ---------------------------------------------------------------------------
+
std::unique_ptr<VerticalShiftGridSet>
VerticalShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) {
if (filename == "null") {
@@ -258,27 +1316,49 @@ VerticalShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) {
return set;
}
- PAFile fp;
- if (!(fp = pj_open_lib(ctx, filename.c_str(), "rb"))) {
+ auto fp = FileManager::open_resource_file(ctx, filename.c_str());
+ if (!fp) {
ctx->last_errno = 0; /* don't treat as a persistent error */
return nullptr;
}
- if (ends_with(filename, "gtx") || ends_with(filename, "GTX")) {
- auto grid = GTXVerticalShiftGrid::open(ctx, fp);
+ const auto actualName(fp->name());
+ if (ends_with(actualName, "gtx") || ends_with(actualName, "GTX")) {
+ auto grid = GTXVerticalShiftGrid::open(ctx, std::move(fp), actualName);
if (!grid) {
- pj_ctx_fclose(ctx, fp);
return nullptr;
}
auto set =
std::unique_ptr<VerticalShiftGridSet>(new VerticalShiftGridSet());
- set->m_name = filename;
+ set->m_name = actualName;
set->m_format = "gtx";
set->m_grids.push_back(std::unique_ptr<VerticalShiftGrid>(grid));
return set;
}
+ /* -------------------------------------------------------------------- */
+ /* Load a header, to determine the file type. */
+ /* -------------------------------------------------------------------- */
+ unsigned char header[4];
+ size_t header_size = fp->read(header, sizeof(header));
+ if (header_size != sizeof(header)) {
+ return nullptr;
+ }
+ fp->seek(0);
+
+ if (IsTIFF(header_size, header)) {
+#ifdef TIFF_ENABLED
+ auto set = GTiffVGridShiftSet::open(ctx, std::move(fp), actualName);
+ if (!set)
+ pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ return set;
+#else
+ pj_log(ctx, PJ_LOG_ERROR,
+ "TIFF grid, but TIFF support disabled in this build");
+ return nullptr;
+#endif
+ }
+
pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Unrecognized vertical grid format");
- pj_ctx_fclose(ctx, fp);
return nullptr;
}
@@ -316,9 +1396,18 @@ const VerticalShiftGrid *VerticalShiftGridSet::gridAt(double lon,
// ---------------------------------------------------------------------------
-HorizontalShiftGrid::HorizontalShiftGrid(int widthIn, int heightIn,
+void VerticalShiftGridSet::reassign_context(PJ_CONTEXT *ctx) {
+ for (const auto &grid : m_grids) {
+ grid->reassign_context(ctx);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+HorizontalShiftGrid::HorizontalShiftGrid(const std::string &nameIn, int widthIn,
+ int heightIn,
const ExtentAndRes &extentIn)
- : Grid(widthIn, heightIn, extentIn) {}
+ : Grid(nameIn, widthIn, heightIn, extentIn) {}
// ---------------------------------------------------------------------------
@@ -337,16 +1426,20 @@ HorizontalShiftGridSet::~HorizontalShiftGridSet() = default;
class NullHorizontalShiftGrid : public HorizontalShiftGrid {
public:
- NullHorizontalShiftGrid() : HorizontalShiftGrid(3, 3, globalExtent()) {}
+ NullHorizontalShiftGrid()
+ : HorizontalShiftGrid("null", 3, 3, globalExtent()) {}
bool isNullGrid() const override { return true; }
- bool valueAt(int, int, float &lonShift, float &latShift) const override;
+ bool valueAt(int, int, bool, float &lonShift,
+ float &latShift) const override;
+
+ void reassign_context(PJ_CONTEXT *) override {}
};
// ---------------------------------------------------------------------------
-bool NullHorizontalShiftGrid::valueAt(int, int, float &lonShift,
+bool NullHorizontalShiftGrid::valueAt(int, int, bool, float &lonShift,
float &latShift) const {
lonShift = 0.0f;
latShift = 0.0f;
@@ -365,39 +1458,46 @@ static double to_double(const void *data) {
class NTv1Grid : public HorizontalShiftGrid {
PJ_CONTEXT *m_ctx;
- PAFile m_fp;
+ std::unique_ptr<File> m_fp;
NTv1Grid(const NTv1Grid &) = delete;
NTv1Grid &operator=(const NTv1Grid &) = delete;
public:
- NTv1Grid(PJ_CONTEXT *ctx, PAFile fp, int widthIn, int heightIn,
- const ExtentAndRes &extentIn)
- : HorizontalShiftGrid(widthIn, heightIn, extentIn), m_ctx(ctx),
- m_fp(fp) {}
+ explicit NTv1Grid(PJ_CONTEXT *ctx, std::unique_ptr<File> &&fp,
+ const std::string &nameIn, int widthIn, int heightIn,
+ const ExtentAndRes &extentIn)
+ : HorizontalShiftGrid(nameIn, widthIn, heightIn, extentIn), m_ctx(ctx),
+ m_fp(std::move(fp)) {}
~NTv1Grid() override;
- bool valueAt(int, int, float &lonShift, float &latShift) const override;
+ bool valueAt(int, int, bool, float &lonShift,
+ float &latShift) const override;
- static NTv1Grid *open(PJ_CONTEXT *ctx, PAFile fp,
+ static NTv1Grid *open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
const std::string &filename);
+
+ void reassign_context(PJ_CONTEXT *ctx) override {
+ m_ctx = ctx;
+ m_fp->reassign_context(ctx);
+ }
};
// ---------------------------------------------------------------------------
-NTv1Grid::~NTv1Grid() { pj_ctx_fclose(m_ctx, m_fp); }
+NTv1Grid::~NTv1Grid() = default;
// ---------------------------------------------------------------------------
-NTv1Grid *NTv1Grid::open(PJ_CONTEXT *ctx, PAFile fp,
+NTv1Grid *NTv1Grid::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
const std::string &filename) {
unsigned char header[192];
/* -------------------------------------------------------------------- */
/* Read the header. */
/* -------------------------------------------------------------------- */
- if (pj_ctx_fread(ctx, header, sizeof(header), 1, fp) != 1) {
+ if (fp->read(header, sizeof(header)) != sizeof(header)) {
pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
return nullptr;
}
@@ -446,21 +1546,20 @@ NTv1Grid *NTv1Grid::open(PJ_CONTEXT *ctx, PAFile fp,
const int rows = static_cast<int>(
fabs((extent.northLat - extent.southLat) / extent.resLat + 0.5) + 1);
- return new NTv1Grid(ctx, fp, columns, rows, extent);
+ return new NTv1Grid(ctx, std::move(fp), filename, columns, rows, extent);
}
// ---------------------------------------------------------------------------
-bool NTv1Grid::valueAt(int x, int y, float &lonShift, float &latShift) const {
+bool NTv1Grid::valueAt(int x, int y, bool compensateNTConvention,
+ float &lonShift, float &latShift) const {
assert(x >= 0 && y >= 0 && x < m_width && y < m_height);
double two_doubles[2];
// NTv1 is organized from east to west !
- pj_ctx_fseek(m_ctx, m_fp,
- 192 + 2 * sizeof(double) * (y * m_width + m_width - 1 - x),
- SEEK_SET);
- if (pj_ctx_fread(m_ctx, &two_doubles[0], sizeof(two_doubles), 1, m_fp) !=
- 1) {
+ m_fp->seek(192 + 2 * sizeof(double) * (y * m_width + m_width - 1 - x));
+ if (m_fp->read(&two_doubles[0], sizeof(two_doubles)) !=
+ sizeof(two_doubles)) {
pj_ctx_set_errno(m_ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
return false;
}
@@ -469,7 +1568,9 @@ bool NTv1Grid::valueAt(int x, int y, float &lonShift, float &latShift) const {
}
/* convert seconds to radians */
latShift = static_cast<float>(two_doubles[0] * ((M_PI / 180.0) / 3600.0));
- lonShift = static_cast<float>(two_doubles[1] * ((M_PI / 180.0) / 3600.0));
+ // west longitude positive convention !
+ lonShift = (compensateNTConvention ? -1 : 1) *
+ static_cast<float>(two_doubles[1] * ((M_PI / 180.0) / 3600.0));
return true;
}
@@ -478,39 +1579,46 @@ bool NTv1Grid::valueAt(int x, int y, float &lonShift, float &latShift) const {
class CTable2Grid : public HorizontalShiftGrid {
PJ_CONTEXT *m_ctx;
- PAFile m_fp;
+ std::unique_ptr<File> m_fp;
CTable2Grid(const CTable2Grid &) = delete;
CTable2Grid &operator=(const CTable2Grid &) = delete;
public:
- CTable2Grid(PJ_CONTEXT *ctx, PAFile fp, int widthIn, int heightIn,
+ CTable2Grid(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
+ const std::string &nameIn, int widthIn, int heightIn,
const ExtentAndRes &extentIn)
- : HorizontalShiftGrid(widthIn, heightIn, extentIn), m_ctx(ctx),
- m_fp(fp) {}
+ : HorizontalShiftGrid(nameIn, widthIn, heightIn, extentIn), m_ctx(ctx),
+ m_fp(std::move(fp)) {}
~CTable2Grid() override;
- bool valueAt(int, int, float &lonShift, float &latShift) const override;
+ bool valueAt(int, int, bool, float &lonShift,
+ float &latShift) const override;
- static CTable2Grid *open(PJ_CONTEXT *ctx, PAFile fp,
+ static CTable2Grid *open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
const std::string &filename);
+
+ void reassign_context(PJ_CONTEXT *ctx) override {
+ m_ctx = ctx;
+ m_fp->reassign_context(ctx);
+ }
};
// ---------------------------------------------------------------------------
-CTable2Grid::~CTable2Grid() { pj_ctx_fclose(m_ctx, m_fp); }
+CTable2Grid::~CTable2Grid() = default;
// ---------------------------------------------------------------------------
-CTable2Grid *CTable2Grid::open(PJ_CONTEXT *ctx, PAFile fp,
+CTable2Grid *CTable2Grid::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
const std::string &filename) {
unsigned char header[160];
/* -------------------------------------------------------------------- */
/* Read the header. */
/* -------------------------------------------------------------------- */
- if (pj_ctx_fread(ctx, header, sizeof(header), 1, fp) != 1) {
+ if (fp->read(header, sizeof(header)) != sizeof(header)) {
pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
return nullptr;
}
@@ -551,19 +1659,18 @@ CTable2Grid *CTable2Grid::open(PJ_CONTEXT *ctx, PAFile fp,
extent.eastLon = extent.westLon + (width - 1) * extent.resLon;
extent.northLat = extent.southLat + (height - 1) * extent.resLon;
- return new CTable2Grid(ctx, fp, width, height, extent);
+ return new CTable2Grid(ctx, std::move(fp), filename, width, height, extent);
}
// ---------------------------------------------------------------------------
-bool CTable2Grid::valueAt(int x, int y, float &lonShift,
- float &latShift) const {
+bool CTable2Grid::valueAt(int x, int y, bool compensateNTConvention,
+ float &lonShift, float &latShift) const {
assert(x >= 0 && y >= 0 && x < m_width && y < m_height);
float two_floats[2];
- pj_ctx_fseek(m_ctx, m_fp, 160 + 2 * sizeof(float) * (y * m_width + x),
- SEEK_SET);
- if (pj_ctx_fread(m_ctx, &two_floats[0], sizeof(two_floats), 1, m_fp) != 1) {
+ m_fp->seek(160 + 2 * sizeof(float) * (y * m_width + x));
+ if (m_fp->read(&two_floats[0], sizeof(two_floats)) != sizeof(two_floats)) {
pj_ctx_set_errno(m_ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
return false;
}
@@ -572,7 +1679,8 @@ bool CTable2Grid::valueAt(int x, int y, float &lonShift,
}
latShift = two_floats[1];
- lonShift = two_floats[0];
+ // west longitude positive convention !
+ lonShift = (compensateNTConvention ? -1 : 1) * two_floats[0];
return true;
}
@@ -580,19 +1688,24 @@ bool CTable2Grid::valueAt(int x, int y, float &lonShift,
// ---------------------------------------------------------------------------
class NTv2GridSet : public HorizontalShiftGridSet {
- PJ_CONTEXT *m_ctx;
- PAFile m_fp;
+ std::unique_ptr<File> m_fp;
NTv2GridSet(const NTv2GridSet &) = delete;
NTv2GridSet &operator=(const NTv2GridSet &) = delete;
- NTv2GridSet(PJ_CONTEXT *ctx, PAFile fp) : m_ctx(ctx), m_fp(fp) {}
+ explicit NTv2GridSet(std::unique_ptr<File> &&fp) : m_fp(std::move(fp)) {}
public:
~NTv2GridSet() override;
- static std::unique_ptr<NTv2GridSet> open(PJ_CONTEXT *ctx, PAFile fp,
+ static std::unique_ptr<NTv2GridSet> open(PJ_CONTEXT *ctx,
+ std::unique_ptr<File> fp,
const std::string &filename);
+
+ void reassign_context(PJ_CONTEXT *ctx) override {
+ HorizontalShiftGridSet::reassign_context(ctx);
+ m_fp->reassign_context(ctx);
+ }
};
// ---------------------------------------------------------------------------
@@ -602,7 +1715,7 @@ class NTv2Grid : public HorizontalShiftGrid {
std::string m_name;
PJ_CONTEXT *m_ctx; // owned by the parent NTv2GridSet
- PAFile m_fp; // owned by the parent NTv2GridSet
+ File *m_fp; // owned by the parent NTv2GridSet
unsigned long long m_offset;
bool m_mustSwap;
@@ -610,30 +1723,36 @@ class NTv2Grid : public HorizontalShiftGrid {
NTv2Grid &operator=(const NTv2Grid &) = delete;
public:
- NTv2Grid(const std::string &nameIn, PJ_CONTEXT *ctx, PAFile fp,
+ NTv2Grid(const std::string &nameIn, PJ_CONTEXT *ctx, File *fp,
unsigned long long offsetIn, bool mustSwapIn, int widthIn,
int heightIn, const ExtentAndRes &extentIn)
- : HorizontalShiftGrid(widthIn, heightIn, extentIn), m_name(nameIn),
- m_ctx(ctx), m_fp(fp), m_offset(offsetIn), m_mustSwap(mustSwapIn) {}
+ : HorizontalShiftGrid(nameIn, widthIn, heightIn, extentIn),
+ m_name(nameIn), m_ctx(ctx), m_fp(fp), m_offset(offsetIn),
+ m_mustSwap(mustSwapIn) {}
+
+ bool valueAt(int, int, bool, float &lonShift,
+ float &latShift) const override;
- bool valueAt(int, int, float &lonShift, float &latShift) const override;
+ void reassign_context(PJ_CONTEXT *ctx) override {
+ m_ctx = ctx;
+ m_fp->reassign_context(ctx);
+ }
};
// ---------------------------------------------------------------------------
-bool NTv2Grid::valueAt(int x, int y, float &lonShift, float &latShift) const {
+bool NTv2Grid::valueAt(int x, int y, bool compensateNTConvention,
+ float &lonShift, float &latShift) const {
assert(x >= 0 && y >= 0 && x < m_width && y < m_height);
float two_float[2];
// NTv2 is organized from east to west !
// there are 4 components: lat shift, lon shift, lat error, lon error
- pj_ctx_fseek(
- m_ctx, m_fp,
- // FIXME when fseek support unsigned long long
- static_cast<long>(m_offset +
- 4 * sizeof(float) * (y * m_width + m_width - 1 - x)),
- SEEK_SET);
- if (pj_ctx_fread(m_ctx, &two_float[0], sizeof(two_float), 1, m_fp) != 1) {
+ m_fp->seek(
+ m_offset +
+ 4 * sizeof(float) *
+ (static_cast<unsigned long long>(y) * m_width + m_width - 1 - x));
+ if (m_fp->read(&two_float[0], sizeof(two_float)) != sizeof(two_float)) {
pj_ctx_set_errno(m_ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
return false;
}
@@ -642,19 +1761,23 @@ bool NTv2Grid::valueAt(int x, int y, float &lonShift, float &latShift) const {
}
/* convert seconds to radians */
latShift = static_cast<float>(two_float[0] * ((M_PI / 180.0) / 3600.0));
- lonShift = static_cast<float>(two_float[1] * ((M_PI / 180.0) / 3600.0));
+ // west longitude positive convention !
+ lonShift = (compensateNTConvention ? -1 : 1) *
+ static_cast<float>(two_float[1] * ((M_PI / 180.0) / 3600.0));
return true;
}
// ---------------------------------------------------------------------------
-NTv2GridSet::~NTv2GridSet() { pj_ctx_fclose(m_ctx, m_fp); }
+NTv2GridSet::~NTv2GridSet() = default;
// ---------------------------------------------------------------------------
-std::unique_ptr<NTv2GridSet> NTv2GridSet::open(PJ_CONTEXT *ctx, PAFile fp,
+std::unique_ptr<NTv2GridSet> NTv2GridSet::open(PJ_CONTEXT *ctx,
+ std::unique_ptr<File> fp,
const std::string &filename) {
- auto set = std::unique_ptr<NTv2GridSet>(new NTv2GridSet(ctx, fp));
+ File *fpRaw = fp.get();
+ auto set = std::unique_ptr<NTv2GridSet>(new NTv2GridSet(std::move(fp)));
set->m_name = filename;
set->m_format = "ntv2";
@@ -663,7 +1786,7 @@ std::unique_ptr<NTv2GridSet> NTv2GridSet::open(PJ_CONTEXT *ctx, PAFile fp,
/* -------------------------------------------------------------------- */
/* Read the header. */
/* -------------------------------------------------------------------- */
- if (pj_ctx_fread(ctx, header, sizeof(header), 1, fp) != 1) {
+ if (fpRaw->read(header, sizeof(header)) != sizeof(header)) {
pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
return nullptr;
}
@@ -692,7 +1815,7 @@ std::unique_ptr<NTv2GridSet> NTv2GridSet::open(PJ_CONTEXT *ctx, PAFile fp,
/* ==================================================================== */
for (unsigned subfile = 0; subfile < num_subfiles; subfile++) {
// Read header
- if (pj_ctx_fread(ctx, header, sizeof(header), 1, fp) != 1) {
+ if (fpRaw->read(header, sizeof(header)) != sizeof(header)) {
pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
return nullptr;
}
@@ -757,9 +1880,10 @@ std::unique_ptr<NTv2GridSet> NTv2GridSet::open(PJ_CONTEXT *ctx, PAFile fp,
return nullptr;
}
- auto offset = pj_ctx_ftell(ctx, fp);
- auto grid = std::unique_ptr<NTv2Grid>(new NTv2Grid(
- gridName, ctx, fp, offset, must_swap, columns, rows, extent));
+ const auto offset = fpRaw->tell();
+ auto grid = std::unique_ptr<NTv2Grid>(
+ new NTv2Grid(filename + ", " + gridName, ctx, fpRaw, offset,
+ must_swap, columns, rows, extent));
std::string parentName;
parentName.assign(header + 24, 8);
auto iter = mapGrids.find(parentName);
@@ -772,11 +1896,293 @@ std::unique_ptr<NTv2GridSet> NTv2GridSet::open(PJ_CONTEXT *ctx, PAFile fp,
mapGrids[gridName] = gridPtr;
// Skip grid data. 4 components of size float
- pj_ctx_fseek(ctx, fp, gs_count * 4 * 4, SEEK_CUR);
+ fpRaw->seek(static_cast<unsigned long long>(gs_count) * 4 * 4,
+ SEEK_CUR);
}
return set;
}
+#ifdef TIFF_ENABLED
+
+// ---------------------------------------------------------------------------
+
+class GTiffHGridShiftSet : public HorizontalShiftGridSet, public GTiffDataset {
+
+ GTiffHGridShiftSet(PJ_CONTEXT *ctx, std::unique_ptr<File> &&fp)
+ : GTiffDataset(ctx, std::move(fp)) {}
+
+ public:
+ ~GTiffHGridShiftSet() override;
+
+ static std::unique_ptr<GTiffHGridShiftSet>
+ open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
+ const std::string &filename);
+
+ void reassign_context(PJ_CONTEXT *ctx) override {
+ HorizontalShiftGridSet::reassign_context(ctx);
+ GTiffDataset::reassign_context(ctx);
+ }
+};
+
+// ---------------------------------------------------------------------------
+
+class GTiffHGrid : public HorizontalShiftGrid {
+ friend void insertIntoHierarchy<GTiffHGrid, HorizontalShiftGrid>(
+ PJ_CONTEXT *ctx, std::unique_ptr<GTiffHGrid> &&grid,
+ const std::string &gridName, const std::string &parentName,
+ std::vector<std::unique_ptr<HorizontalShiftGrid>> &topGrids,
+ std::map<std::string, GTiffHGrid *> &mapGrids);
+
+ std::unique_ptr<GTiffGrid> m_grid;
+ uint16 m_idxLatShift;
+ uint16 m_idxLonShift;
+ double m_convFactorToRadian;
+ bool m_positiveEast;
+
+ public:
+ GTiffHGrid(std::unique_ptr<GTiffGrid> &&grid, uint16 idxLatShift,
+ uint16 idxLonShift, double convFactorToRadian,
+ bool positiveEast);
+
+ ~GTiffHGrid() override;
+
+ bool valueAt(int x, int y, bool, float &lonShift,
+ float &latShift) const override;
+
+ void insertGrid(PJ_CONTEXT *ctx, std::unique_ptr<GTiffHGrid> &&subgrid);
+
+ void reassign_context(PJ_CONTEXT *ctx) override {
+ m_grid->reassign_context(ctx);
+ }
+};
+
+// ---------------------------------------------------------------------------
+
+GTiffHGridShiftSet::~GTiffHGridShiftSet() = default;
+
+// ---------------------------------------------------------------------------
+
+GTiffHGrid::GTiffHGrid(std::unique_ptr<GTiffGrid> &&grid, uint16 idxLatShift,
+ uint16 idxLonShift, double convFactorToRadian,
+ bool positiveEast)
+ : HorizontalShiftGrid(grid->name(), grid->width(), grid->height(),
+ grid->extentAndRes()),
+ m_grid(std::move(grid)), m_idxLatShift(idxLatShift),
+ m_idxLonShift(idxLonShift), m_convFactorToRadian(convFactorToRadian),
+ m_positiveEast(positiveEast) {}
+
+// ---------------------------------------------------------------------------
+
+GTiffHGrid::~GTiffHGrid() = default;
+
+// ---------------------------------------------------------------------------
+
+bool GTiffHGrid::valueAt(int x, int y, bool, float &lonShift,
+ float &latShift) const {
+ if (!m_grid->valueAt(m_idxLatShift, x, y, latShift) ||
+ !m_grid->valueAt(m_idxLonShift, x, y, lonShift)) {
+ return false;
+ }
+ // From arc-seconds to radians
+ latShift = static_cast<float>(latShift * m_convFactorToRadian);
+ lonShift = static_cast<float>(lonShift * m_convFactorToRadian);
+ if (!m_positiveEast) {
+ lonShift = -lonShift;
+ }
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+void GTiffHGrid::insertGrid(PJ_CONTEXT *ctx,
+ std::unique_ptr<GTiffHGrid> &&subgrid) {
+ bool gridInserted = false;
+ const auto &extent = subgrid->extentAndRes();
+ for (const auto &candidateParent : m_children) {
+ const auto &candidateParentExtent = candidateParent->extentAndRes();
+ if (candidateParentExtent.contains(extent)) {
+ static_cast<GTiffHGrid *>(candidateParent.get())
+ ->insertGrid(ctx, std::move(subgrid));
+ gridInserted = true;
+ break;
+ } else if (candidateParentExtent.intersects(extent)) {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "Partially intersecting grids found!");
+ }
+ }
+ if (!gridInserted) {
+ m_children.emplace_back(std::move(subgrid));
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<GTiffHGridShiftSet>
+GTiffHGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
+ const std::string &filename) {
+ auto set = std::unique_ptr<GTiffHGridShiftSet>(
+ new GTiffHGridShiftSet(ctx, std::move(fp)));
+ set->m_name = filename;
+ set->m_format = "gtiff";
+ if (!set->openTIFF(filename)) {
+ return nullptr;
+ }
+
+ // Defaults inspired from NTv2
+ uint16 idxLatShift = 0;
+ uint16 idxLonShift = 1;
+ constexpr double ARC_SECOND_TO_RADIAN = (M_PI / 180.0) / 3600.0;
+ double convFactorToRadian = ARC_SECOND_TO_RADIAN;
+ bool positiveEast = true;
+
+ std::map<std::string, GTiffHGrid *> mapGrids;
+ for (int ifd = 0;; ++ifd) {
+ auto grid = set->nextGrid();
+ if (!grid) {
+ if (ifd == 0) {
+ return nullptr;
+ }
+ break;
+ }
+
+ const auto subfileType = grid->subfileType();
+ if (subfileType != 0 && subfileType != FILETYPE_PAGE) {
+ if (ifd == 0) {
+ pj_log(ctx, PJ_LOG_ERROR, "Invalid subfileType");
+ return nullptr;
+ } else {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "Ignoring IFD %d as it has a unsupported subfileType",
+ ifd);
+ continue;
+ }
+ }
+
+ if (grid->samplesPerPixel() < 2) {
+ if (ifd == 0) {
+ pj_log(ctx, PJ_LOG_ERROR,
+ "At least 2 samples per pixel needed");
+ return nullptr;
+ } else {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "Ignoring IFD %d as it has not at least 2 samples", ifd);
+ continue;
+ }
+ }
+
+ // Identify the index of the latitude and longitude offset channels
+ bool foundDescriptionForAtLeastOneSample = false;
+ bool foundDescriptionForLatOffset = false;
+ bool foundDescriptionForLonOffset = false;
+ for (int i = 0; i < static_cast<int>(grid->samplesPerPixel()); ++i) {
+ const auto desc = grid->metadataItem("DESCRIPTION", i);
+ if (!desc.empty()) {
+ foundDescriptionForAtLeastOneSample = true;
+ }
+ if (desc == "latitude_offset") {
+ idxLatShift = static_cast<uint16>(i);
+ foundDescriptionForLatOffset = true;
+ } else if (desc == "longitude_offset") {
+ idxLonShift = static_cast<uint16>(i);
+ foundDescriptionForLonOffset = true;
+ }
+ }
+
+ if (foundDescriptionForAtLeastOneSample) {
+ if (!foundDescriptionForLonOffset &&
+ !foundDescriptionForLatOffset) {
+ if (ifd > 0) {
+ // Assuming that extra IFD without
+ // longitude_offset/latitude_offset can be ignored
+ // One could imagine to put the accuracy values in separate
+ // IFD for example
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "Ignoring IFD %d as it has no "
+ "longitude_offset/latitude_offset channel",
+ ifd);
+ continue;
+ } else {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "IFD 0 has channel descriptions, but no "
+ "longitude_offset/latitude_offset channel");
+ return nullptr;
+ }
+ }
+ }
+ if (foundDescriptionForLatOffset && !foundDescriptionForLonOffset) {
+ pj_log(ctx, PJ_LOG_ERROR,
+ "Found latitude_offset channel, but not longitude_offset");
+ return nullptr;
+ } else if (foundDescriptionForLonOffset &&
+ !foundDescriptionForLatOffset) {
+ pj_log(ctx, PJ_LOG_ERROR,
+ "Found longitude_offset channel, but not latitude_offset");
+ return nullptr;
+ }
+
+ if (idxLatShift >= grid->samplesPerPixel() ||
+ idxLonShift >= grid->samplesPerPixel()) {
+ pj_log(ctx, PJ_LOG_ERROR, "Invalid sample index");
+ return nullptr;
+ }
+
+ if (foundDescriptionForLonOffset) {
+ const std::string positiveValue =
+ grid->metadataItem("positive_value", idxLonShift);
+ if (!positiveValue.empty()) {
+ if (positiveValue == "west") {
+ positiveEast = false;
+ } else if (positiveValue == "east") {
+ positiveEast = true;
+ } else {
+ pj_log(ctx, PJ_LOG_ERROR,
+ "Unsupported value %s for 'positive_value'",
+ positiveValue.c_str());
+ return nullptr;
+ }
+ }
+ }
+
+ // Identify their unit
+ {
+ const auto unitLatShift =
+ grid->metadataItem("UNITTYPE", idxLatShift);
+ const auto unitLonShift =
+ grid->metadataItem("UNITTYPE", idxLonShift);
+ if (unitLatShift != unitLonShift) {
+ pj_log(ctx, PJ_LOG_ERROR,
+ "Different unit for longitude and latitude offset");
+ return nullptr;
+ }
+ if (!unitLatShift.empty()) {
+ if (unitLatShift == "arc-second") {
+ convFactorToRadian = ARC_SECOND_TO_RADIAN;
+ } else if (unitLatShift == "radian") {
+ convFactorToRadian = 1.0;
+ } else if (unitLatShift == "degree") {
+ convFactorToRadian = M_PI / 180.0;
+ } else {
+ pj_log(ctx, PJ_LOG_ERROR, "Unsupported unit %s",
+ unitLatShift.c_str());
+ return nullptr;
+ }
+ }
+ }
+
+ const std::string gridName = grid->metadataItem("grid_name");
+ const std::string parentName = grid->metadataItem("parent_grid_name");
+
+ auto hgrid = internal::make_unique<GTiffHGrid>(
+ std::move(grid), idxLatShift, idxLonShift, convFactorToRadian,
+ positiveEast);
+
+ insertIntoHierarchy(ctx, std::move(hgrid), gridName, parentName,
+ set->m_grids, mapGrids);
+ }
+ return set;
+}
+#endif // TIFF_ENABLED
+
// ---------------------------------------------------------------------------
std::unique_ptr<HorizontalShiftGridSet>
@@ -791,27 +2197,26 @@ HorizontalShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) {
return set;
}
- PAFile fp;
- if (!(fp = pj_open_lib(ctx, filename.c_str(), "rb"))) {
+ auto fp = FileManager::open_resource_file(ctx, filename.c_str());
+ if (!fp) {
ctx->last_errno = 0; /* don't treat as a persistent error */
return nullptr;
}
+ const auto actualName(fp->name());
char header[160];
/* -------------------------------------------------------------------- */
/* Load a header, to determine the file type. */
/* -------------------------------------------------------------------- */
- size_t header_size;
- if ((header_size = pj_ctx_fread(ctx, header, 1, sizeof(header), fp)) !=
- sizeof(header)) {
+ size_t header_size = fp->read(header, sizeof(header));
+ if (header_size != sizeof(header)) {
/* some files may be smaller that sizeof(header), eg 160, so */
ctx->last_errno = 0; /* don't treat as a persistent error */
pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
"pj_gridinfo_init: short header read of %d bytes",
(int)header_size);
}
-
- pj_ctx_fseek(ctx, fp, SEEK_SET, 0);
+ fp->seek(0);
/* -------------------------------------------------------------------- */
/* Determine file type. */
@@ -819,37 +2224,46 @@ HorizontalShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) {
if (header_size >= 144 + 16 && strncmp(header + 0, "HEADER", 6) == 0 &&
strncmp(header + 96, "W GRID", 6) == 0 &&
strncmp(header + 144, "TO NAD83 ", 16) == 0) {
- auto grid = NTv1Grid::open(ctx, fp, filename);
+ auto grid = NTv1Grid::open(ctx, std::move(fp), actualName);
if (!grid) {
- pj_ctx_fclose(ctx, fp);
return nullptr;
}
auto set = std::unique_ptr<HorizontalShiftGridSet>(
new HorizontalShiftGridSet());
- set->m_name = filename;
+ set->m_name = actualName;
set->m_format = "ntv1";
set->m_grids.push_back(std::unique_ptr<HorizontalShiftGrid>(grid));
return set;
} else if (header_size >= 9 && strncmp(header + 0, "CTABLE V2", 9) == 0) {
- auto grid = CTable2Grid::open(ctx, fp, filename);
+ auto grid = CTable2Grid::open(ctx, std::move(fp), actualName);
if (!grid) {
- pj_ctx_fclose(ctx, fp);
return nullptr;
}
auto set = std::unique_ptr<HorizontalShiftGridSet>(
new HorizontalShiftGridSet());
- set->m_name = filename;
+ set->m_name = actualName;
set->m_format = "ctable2";
set->m_grids.push_back(std::unique_ptr<HorizontalShiftGrid>(grid));
return set;
} else if (header_size >= 48 + 7 &&
strncmp(header + 0, "NUM_OREC", 8) == 0 &&
strncmp(header + 48, "GS_TYPE", 7) == 0) {
- return NTv2GridSet::open(ctx, fp, filename);
+ return NTv2GridSet::open(ctx, std::move(fp), actualName);
+ } else if (IsTIFF(header_size,
+ reinterpret_cast<const unsigned char *>(header))) {
+#ifdef TIFF_ENABLED
+ auto set = GTiffHGridShiftSet::open(ctx, std::move(fp), actualName);
+ if (!set)
+ pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ return set;
+#else
+ pj_log(ctx, PJ_LOG_ERROR,
+ "TIFF grid, but TIFF support disabled in this build");
+ return nullptr;
+#endif
}
pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Unrecognized horizontal grid format");
- pj_ctx_fclose(ctx, fp);
return nullptr;
}
@@ -892,4 +2306,342 @@ const HorizontalShiftGrid *HorizontalShiftGridSet::gridAt(double lon,
return nullptr;
}
+// ---------------------------------------------------------------------------
+
+void HorizontalShiftGridSet::reassign_context(PJ_CONTEXT *ctx) {
+ for (const auto &grid : m_grids) {
+ grid->reassign_context(ctx);
+ }
+}
+
+#ifdef TIFF_ENABLED
+// ---------------------------------------------------------------------------
+
+class GTiffGenericGridShiftSet : public GenericShiftGridSet,
+ public GTiffDataset {
+
+ GTiffGenericGridShiftSet(PJ_CONTEXT *ctx, std::unique_ptr<File> &&fp)
+ : GTiffDataset(ctx, std::move(fp)) {}
+
+ public:
+ ~GTiffGenericGridShiftSet() override;
+
+ static std::unique_ptr<GTiffGenericGridShiftSet>
+ open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
+ const std::string &filename);
+
+ void reassign_context(PJ_CONTEXT *ctx) override {
+ GenericShiftGridSet::reassign_context(ctx);
+ GTiffDataset::reassign_context(ctx);
+ }
+};
+
+// ---------------------------------------------------------------------------
+
+class GTiffGenericGrid : public GenericShiftGrid {
+ friend void insertIntoHierarchy<GTiffGenericGrid, GenericShiftGrid>(
+ PJ_CONTEXT *ctx, std::unique_ptr<GTiffGenericGrid> &&grid,
+ const std::string &gridName, const std::string &parentName,
+ std::vector<std::unique_ptr<GenericShiftGrid>> &topGrids,
+ std::map<std::string, GTiffGenericGrid *> &mapGrids);
+
+ std::unique_ptr<GTiffGrid> m_grid;
+
+ public:
+ GTiffGenericGrid(std::unique_ptr<GTiffGrid> &&grid);
+
+ ~GTiffGenericGrid() override;
+
+ bool valueAt(int x, int y, int sample, float &out) const override;
+
+ int samplesPerPixel() const override { return m_grid->samplesPerPixel(); }
+
+ std::string unit(int sample) const override {
+ return m_grid->metadataItem("UNITTYPE", sample);
+ }
+
+ std::string description(int sample) const override {
+ return m_grid->metadataItem("DESCRIPTION", sample);
+ }
+
+ std::string metadataItem(const std::string &key,
+ int sample = -1) const override {
+ return m_grid->metadataItem(key, sample);
+ }
+
+ void insertGrid(PJ_CONTEXT *ctx,
+ std::unique_ptr<GTiffGenericGrid> &&subgrid);
+
+ void reassign_context(PJ_CONTEXT *ctx) override {
+ m_grid->reassign_context(ctx);
+ }
+};
+
+// ---------------------------------------------------------------------------
+
+GTiffGenericGridShiftSet::~GTiffGenericGridShiftSet() = default;
+
+// ---------------------------------------------------------------------------
+
+GTiffGenericGrid::GTiffGenericGrid(std::unique_ptr<GTiffGrid> &&grid)
+ : GenericShiftGrid(grid->name(), grid->width(), grid->height(),
+ grid->extentAndRes()),
+ m_grid(std::move(grid)) {}
+
+// ---------------------------------------------------------------------------
+
+GTiffGenericGrid::~GTiffGenericGrid() = default;
+
+// ---------------------------------------------------------------------------
+
+bool GTiffGenericGrid::valueAt(int x, int y, int sample, float &out) const {
+ if (sample < 0 ||
+ static_cast<unsigned>(sample) >= m_grid->samplesPerPixel())
+ return false;
+ return m_grid->valueAt(static_cast<uint16>(sample), x, y, out);
+}
+
+// ---------------------------------------------------------------------------
+
+void GTiffGenericGrid::insertGrid(PJ_CONTEXT *ctx,
+ std::unique_ptr<GTiffGenericGrid> &&subgrid) {
+ bool gridInserted = false;
+ const auto &extent = subgrid->extentAndRes();
+ for (const auto &candidateParent : m_children) {
+ const auto &candidateParentExtent = candidateParent->extentAndRes();
+ if (candidateParentExtent.contains(extent)) {
+ static_cast<GTiffGenericGrid *>(candidateParent.get())
+ ->insertGrid(ctx, std::move(subgrid));
+ gridInserted = true;
+ break;
+ } else if (candidateParentExtent.intersects(extent)) {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "Partially intersecting grids found!");
+ }
+ }
+ if (!gridInserted) {
+ m_children.emplace_back(std::move(subgrid));
+ }
+}
+#endif // TIFF_ENABLED
+
+// ---------------------------------------------------------------------------
+
+class NullGenericShiftGrid : public GenericShiftGrid {
+
+ public:
+ NullGenericShiftGrid() : GenericShiftGrid("null", 3, 3, globalExtent()) {}
+
+ bool isNullGrid() const override { return true; }
+ bool valueAt(int, int, int, float &out) const override;
+
+ int samplesPerPixel() const override { return 0; }
+
+ std::string unit(int) const override { return std::string(); }
+
+ std::string description(int) const override { return std::string(); }
+
+ std::string metadataItem(const std::string &, int) const override {
+ return std::string();
+ }
+
+ void reassign_context(PJ_CONTEXT *) override {}
+};
+
+// ---------------------------------------------------------------------------
+
+bool NullGenericShiftGrid::valueAt(int, int, int, float &out) const {
+ out = 0.0f;
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+#ifdef TIFF_ENABLED
+
+std::unique_ptr<GTiffGenericGridShiftSet>
+GTiffGenericGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
+ const std::string &filename) {
+ auto set = std::unique_ptr<GTiffGenericGridShiftSet>(
+ new GTiffGenericGridShiftSet(ctx, std::move(fp)));
+ set->m_name = filename;
+ set->m_format = "gtiff";
+ if (!set->openTIFF(filename)) {
+ return nullptr;
+ }
+
+ std::map<std::string, GTiffGenericGrid *> mapGrids;
+ for (int ifd = 0;; ++ifd) {
+ auto grid = set->nextGrid();
+ if (!grid) {
+ if (ifd == 0) {
+ return nullptr;
+ }
+ break;
+ }
+
+ const auto subfileType = grid->subfileType();
+ if (subfileType != 0 && subfileType != FILETYPE_PAGE) {
+ if (ifd == 0) {
+ pj_log(ctx, PJ_LOG_ERROR, "Invalid subfileType");
+ return nullptr;
+ } else {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "Ignoring IFD %d as it has a unsupported subfileType",
+ ifd);
+ continue;
+ }
+ }
+
+ const std::string gridName = grid->metadataItem("grid_name");
+ const std::string parentName = grid->metadataItem("parent_grid_name");
+
+ auto hgrid = internal::make_unique<GTiffGenericGrid>(std::move(grid));
+
+ insertIntoHierarchy(ctx, std::move(hgrid), gridName, parentName,
+ set->m_grids, mapGrids);
+ }
+ return set;
+}
+#endif // TIFF_ENABLED
+
+// ---------------------------------------------------------------------------
+
+GenericShiftGrid::GenericShiftGrid(const std::string &nameIn, int widthIn,
+ int heightIn, const ExtentAndRes &extentIn)
+ : Grid(nameIn, widthIn, heightIn, extentIn) {}
+
+// ---------------------------------------------------------------------------
+
+GenericShiftGrid::~GenericShiftGrid() = default;
+
+// ---------------------------------------------------------------------------
+
+GenericShiftGridSet::GenericShiftGridSet() = default;
+
+// ---------------------------------------------------------------------------
+
+GenericShiftGridSet::~GenericShiftGridSet() = default;
+
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<GenericShiftGridSet>
+GenericShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) {
+ if (filename == "null") {
+ auto set =
+ std::unique_ptr<GenericShiftGridSet>(new GenericShiftGridSet());
+ set->m_name = filename;
+ set->m_format = "null";
+ set->m_grids.push_back(
+ std::unique_ptr<NullGenericShiftGrid>(new NullGenericShiftGrid()));
+ return set;
+ }
+
+ auto fp = FileManager::open_resource_file(ctx, filename.c_str());
+ if (!fp) {
+ ctx->last_errno = 0; /* don't treat as a persistent error */
+ return nullptr;
+ }
+ const auto actualName(fp->name());
+
+ /* -------------------------------------------------------------------- */
+ /* Load a header, to determine the file type. */
+ /* -------------------------------------------------------------------- */
+ unsigned char header[4];
+ size_t header_size = fp->read(header, sizeof(header));
+ if (header_size != sizeof(header)) {
+ return nullptr;
+ }
+ fp->seek(0);
+
+ if (IsTIFF(header_size, header)) {
+#ifdef TIFF_ENABLED
+ auto set =
+ GTiffGenericGridShiftSet::open(ctx, std::move(fp), actualName);
+ if (!set)
+ pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ return set;
+#else
+ pj_log(ctx, PJ_LOG_ERROR,
+ "TIFF grid, but TIFF support disabled in this build");
+ return nullptr;
+#endif
+ }
+
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Unrecognized generic grid format");
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+const GenericShiftGrid *GenericShiftGrid::gridAt(double lon, double lat) const {
+ for (const auto &child : m_children) {
+ const auto &extentChild = child->extentAndRes();
+ if ((extentChild.fullWorldLongitude() ||
+ (lon >= extentChild.westLon && lon <= extentChild.eastLon)) &&
+ lat >= extentChild.southLat && lat <= extentChild.northLat) {
+ return child->gridAt(lon, lat);
+ }
+ }
+ return this;
+}
+
+// ---------------------------------------------------------------------------
+
+const GenericShiftGrid *GenericShiftGridSet::gridAt(double lon,
+ double lat) const {
+ for (const auto &grid : m_grids) {
+ if (dynamic_cast<NullGenericShiftGrid *>(grid.get())) {
+ return grid.get();
+ }
+ const auto &extent = grid->extentAndRes();
+ if ((extent.fullWorldLongitude() ||
+ (lon >= extent.westLon && lon <= extent.eastLon)) &&
+ lat >= extent.southLat && lat <= extent.northLat) {
+ return grid->gridAt(lon, lat);
+ }
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+void GenericShiftGridSet::reassign_context(PJ_CONTEXT *ctx) {
+ for (const auto &grid : m_grids) {
+ grid->reassign_context(ctx);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+ListOfGenericGrids proj_generic_grid_init(PJ *P, const char *gridkey) {
+ std::string key("s");
+ key += gridkey;
+ const char *gridnames = pj_param(P->ctx, P->params, key.c_str()).s;
+ if (gridnames == nullptr)
+ return {};
+
+ auto listOfGridNames = internal::split(std::string(gridnames), ',');
+ ListOfGenericGrids grids;
+ for (const auto &gridnameStr : listOfGridNames) {
+ const char *gridname = gridnameStr.c_str();
+ bool canFail = false;
+ if (gridname[0] == '@') {
+ canFail = true;
+ gridname++;
+ }
+ auto gridSet = GenericShiftGridSet::open(P->ctx, gridname);
+ if (!gridSet) {
+ if (!canFail) {
+ pj_ctx_set_errno(P->ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ return {};
+ }
+ } else {
+ grids.emplace_back(std::move(gridSet));
+ }
+ }
+
+ return grids;
+}
+
NS_PROJ_END
diff --git a/src/grids.hpp b/src/grids.hpp
index 65bf502b..aa852ef6 100644
--- a/src/grids.hpp
+++ b/src/grids.hpp
@@ -45,17 +45,21 @@ struct ExtentAndRes {
double resLat; // in radian
bool fullWorldLongitude() const;
+ bool contains(const ExtentAndRes &other) const;
+ bool intersects(const ExtentAndRes &other) const;
};
// ---------------------------------------------------------------------------
class Grid {
protected:
+ std::string m_name;
int m_width;
int m_height;
ExtentAndRes m_extent;
- Grid(int widthIn, int heightIn, const ExtentAndRes &extentIn);
+ Grid(const std::string &nameIn, int widthIn, int heightIn,
+ const ExtentAndRes &extentIn);
public:
virtual ~Grid();
@@ -63,6 +67,7 @@ class Grid {
int width() const { return m_width; }
int height() const { return m_height; }
const ExtentAndRes &extentAndRes() const { return m_extent; }
+ const std::string &name() const { return m_name; }
virtual bool isNullGrid() const { return false; }
};
@@ -74,7 +79,8 @@ class VerticalShiftGrid : public Grid {
std::vector<std::unique_ptr<VerticalShiftGrid>> m_children{};
public:
- VerticalShiftGrid(int widthIn, int heightIn, const ExtentAndRes &extentIn);
+ VerticalShiftGrid(const std::string &nameIn, int widthIn, int heightIn,
+ const ExtentAndRes &extentIn);
const VerticalShiftGrid *gridAt(double lon, double lat) const;
@@ -82,11 +88,14 @@ class VerticalShiftGrid : public Grid {
// x = 0 is western-most column, y = 0 is southern-most line
virtual bool valueAt(int x, int y, float &out) const = 0;
+
+ virtual void reassign_context(PJ_CONTEXT *ctx) = 0;
};
// ---------------------------------------------------------------------------
class VerticalShiftGridSet {
+ protected:
std::string m_name{};
std::string m_format{};
std::vector<std::unique_ptr<VerticalShiftGrid>> m_grids{};
@@ -105,6 +114,8 @@ class VerticalShiftGridSet {
return m_grids;
}
const VerticalShiftGrid *gridAt(double lon, double lat) const;
+
+ virtual void reassign_context(PJ_CONTEXT *ctx);
};
// ---------------------------------------------------------------------------
@@ -114,15 +125,17 @@ class HorizontalShiftGrid : public Grid {
std::vector<std::unique_ptr<HorizontalShiftGrid>> m_children{};
public:
- HorizontalShiftGrid(int widthIn, int heightIn,
+ HorizontalShiftGrid(const std::string &nameIn, int widthIn, int heightIn,
const ExtentAndRes &extentIn);
~HorizontalShiftGrid() override;
const HorizontalShiftGrid *gridAt(double lon, double lat) const;
// x = 0 is western-most column, y = 0 is southern-most line
- virtual bool valueAt(int x, int y, float &lonShift,
- float &latShift) const = 0;
+ virtual bool valueAt(int x, int y, bool compensateNTConvention,
+ float &lonShift, float &latShift) const = 0;
+
+ virtual void reassign_context(PJ_CONTEXT *ctx) = 0;
};
// ---------------------------------------------------------------------------
@@ -147,15 +160,75 @@ class HorizontalShiftGridSet {
return m_grids;
}
const HorizontalShiftGrid *gridAt(double lon, double lat) const;
+
+ virtual void reassign_context(PJ_CONTEXT *ctx);
+};
+
+// ---------------------------------------------------------------------------
+
+class GenericShiftGrid : public Grid {
+ protected:
+ std::vector<std::unique_ptr<GenericShiftGrid>> m_children{};
+
+ public:
+ GenericShiftGrid(const std::string &nameIn, int widthIn, int heightIn,
+ const ExtentAndRes &extentIn);
+
+ ~GenericShiftGrid() override;
+
+ const GenericShiftGrid *gridAt(double lon, double lat) const;
+
+ virtual std::string unit(int sample) const = 0;
+
+ virtual std::string description(int sample) const = 0;
+
+ virtual std::string metadataItem(const std::string &key,
+ int sample = -1) const = 0;
+
+ virtual int samplesPerPixel() const = 0;
+
+ // x = 0 is western-most column, y = 0 is southern-most line
+ virtual bool valueAt(int x, int y, int sample, float &out) const = 0;
+
+ virtual void reassign_context(PJ_CONTEXT *ctx) = 0;
+};
+
+// ---------------------------------------------------------------------------
+
+class GenericShiftGridSet {
+ protected:
+ std::string m_name{};
+ std::string m_format{};
+ std::vector<std::unique_ptr<GenericShiftGrid>> m_grids{};
+
+ GenericShiftGridSet();
+
+ public:
+ virtual ~GenericShiftGridSet();
+
+ static std::unique_ptr<GenericShiftGridSet>
+ open(PJ_CONTEXT *ctx, const std::string &filename);
+
+ const std::string &name() const { return m_name; }
+ const std::string &format() const { return m_format; }
+ const std::vector<std::unique_ptr<GenericShiftGrid>> &grids() const {
+ return m_grids;
+ }
+ const GenericShiftGrid *gridAt(double lon, double lat) const;
+
+ virtual void reassign_context(PJ_CONTEXT *ctx);
};
// ---------------------------------------------------------------------------
typedef std::vector<std::unique_ptr<HorizontalShiftGridSet>> ListOfHGrids;
typedef std::vector<std::unique_ptr<VerticalShiftGridSet>> ListOfVGrids;
+typedef std::vector<std::unique_ptr<GenericShiftGridSet>> ListOfGenericGrids;
ListOfVGrids proj_vgrid_init(PJ *P, const char *grids);
ListOfHGrids proj_hgrid_init(PJ *P, const char *grids);
+ListOfGenericGrids proj_generic_grid_init(PJ *P, const char *grids);
+
double proj_vgrid_value(PJ *P, const ListOfVGrids &, PJ_LP lp,
double vmultiplier);
PJ_LP proj_hgrid_value(PJ *P, const ListOfHGrids &, PJ_LP lp);
diff --git a/src/iso19111/c_api.cpp b/src/iso19111/c_api.cpp
index 9db9e5b3..5df4c513 100644
--- a/src/iso19111/c_api.cpp
+++ b/src/iso19111/c_api.cpp
@@ -177,7 +177,11 @@ static PJ *pj_obj_create(PJ_CONTEXT *ctx, const IdentifiedObjectNNPtr &objIn) {
auto formatter = PROJStringFormatter::create(
PROJStringFormatter::Convention::PROJ_5, dbContext);
auto projString = coordop->exportToPROJString(formatter.get());
+ if (pj_context_is_network_enabled(ctx)) {
+ ctx->defer_grid_opening = true;
+ }
auto pj = pj_create_internal(ctx, projString.c_str());
+ ctx->defer_grid_opening = false;
if (pj) {
pj->iso_obj = objIn;
if (ctx->cpp_context) {
@@ -766,7 +770,7 @@ int PROJ_DLL proj_grid_get_info_from_database(
bool open_license;
bool available;
if (!db_context->lookForGridInfo(
- grid_name, ctx->cpp_context->lastGridFullName_,
+ grid_name, false, ctx->cpp_context->lastGridFullName_,
ctx->cpp_context->lastGridPackageName_,
ctx->cpp_context->lastGridUrl_, direct_download, open_license,
available)) {
@@ -6571,7 +6575,10 @@ int proj_coordoperation_is_instantiable(PJ_CONTEXT *ctx,
}
auto dbContext = getDBcontextNoException(ctx, __FUNCTION__);
try {
- auto ret = op->isPROJInstantiable(dbContext) ? 1 : 0;
+ auto ret = op->isPROJInstantiable(dbContext,
+ pj_context_is_network_enabled(ctx))
+ ? 1
+ : 0;
if (ctx->cpp_context) {
ctx->cpp_context->autoCloseDbIfNeeded();
}
@@ -6883,7 +6890,8 @@ int proj_coordoperation_get_grid_used_count(PJ_CONTEXT *ctx,
try {
if (!coordoperation->gridsNeededAsked) {
coordoperation->gridsNeededAsked = true;
- const auto gridsNeeded = co->gridsNeeded(dbContext);
+ const auto gridsNeeded =
+ co->gridsNeeded(dbContext, pj_context_is_network_enabled(ctx));
for (const auto &gridDesc : gridsNeeded) {
coordoperation->gridsNeeded.emplace_back(gridDesc);
}
@@ -7220,6 +7228,12 @@ void PROJ_DLL proj_operation_factory_context_set_grid_availability_use(
CoordinateOperationContext::GridAvailabilityUse::
IGNORE_GRID_AVAILABILITY);
break;
+
+ case PROJ_GRID_AVAILABILITY_KNOWN_AVAILABLE:
+ factory_ctx->operationContext->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ KNOWN_AVAILABLE);
+ break;
}
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
diff --git a/src/iso19111/coordinateoperation.cpp b/src/iso19111/coordinateoperation.cpp
index 6120c768..7c0515c7 100644
--- a/src/iso19111/coordinateoperation.cpp
+++ b/src/iso19111/coordinateoperation.cpp
@@ -788,13 +788,15 @@ void CoordinateOperation::setAccuracies(
* available.
*/
bool CoordinateOperation::isPROJInstantiable(
- const io::DatabaseContextPtr &databaseContext) const {
+ const io::DatabaseContextPtr &databaseContext,
+ bool considerKnownGridsAsAvailable) const {
try {
exportToPROJString(io::PROJStringFormatter::create().get());
} catch (const std::exception &) {
return false;
}
- for (const auto &gridDesc : gridsNeeded(databaseContext)) {
+ for (const auto &gridDesc :
+ gridsNeeded(databaseContext, considerKnownGridsAsAvailable)) {
if (!gridDesc.available) {
return false;
}
@@ -2013,8 +2015,9 @@ bool SingleOperation::_isEquivalentTo(const util::IComparable *other,
// ---------------------------------------------------------------------------
-std::set<GridDescription> SingleOperation::gridsNeeded(
- const io::DatabaseContextPtr &databaseContext) const {
+std::set<GridDescription>
+SingleOperation::gridsNeeded(const io::DatabaseContextPtr &databaseContext,
+ bool considerKnownGridsAsAvailable) const {
std::set<GridDescription> res;
for (const auto &genOpParamvalue : parameterValues()) {
auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
@@ -2026,9 +2029,9 @@ std::set<GridDescription> SingleOperation::gridsNeeded(
desc.shortName = value->valueFile();
if (databaseContext) {
databaseContext->lookForGridInfo(
- desc.shortName, desc.fullName, desc.packageName,
- desc.url, desc.directDownload, desc.openLicense,
- desc.available);
+ desc.shortName, considerKnownGridsAsAvailable,
+ desc.fullName, desc.packageName, desc.url,
+ desc.directDownload, desc.openLicense, desc.available);
}
res.insert(desc);
}
@@ -10209,10 +10212,12 @@ bool ConcatenatedOperation::_isEquivalentTo(
// ---------------------------------------------------------------------------
std::set<GridDescription> ConcatenatedOperation::gridsNeeded(
- const io::DatabaseContextPtr &databaseContext) const {
+ const io::DatabaseContextPtr &databaseContext,
+ bool considerKnownGridsAsAvailable) const {
std::set<GridDescription> res;
for (const auto &operation : operations()) {
- const auto l_gridsNeeded = operation->gridsNeeded(databaseContext);
+ const auto l_gridsNeeded = operation->gridsNeeded(
+ databaseContext, considerKnownGridsAsAvailable);
for (const auto &gridDesc : l_gridsNeeded) {
res.insert(gridDesc);
}
@@ -11132,7 +11137,10 @@ struct FilterResults {
bool gridsKnown = true;
if (context->getAuthorityFactory()) {
const auto gridsNeeded = op->gridsNeeded(
- context->getAuthorityFactory()->databaseContext());
+ context->getAuthorityFactory()->databaseContext(),
+ gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ KNOWN_AVAILABLE);
for (const auto &gridDesc : gridsNeeded) {
hasGrids = true;
if (gridAvailabilityUse ==
@@ -11254,6 +11262,7 @@ struct FilterResults {
CoordinateOperationPtr lastOp;
bool first = true;
+ const auto gridAvailabilityUse = context->getGridAvailabilityUse();
for (const auto &op : res) {
const auto curAccuracy = getAccuracy(op);
bool dummy = false;
@@ -11266,7 +11275,10 @@ struct FilterResults {
if (context->getAuthorityFactory()) {
const auto gridsNeeded = op->gridsNeeded(
- context->getAuthorityFactory()->databaseContext());
+ context->getAuthorityFactory()->databaseContext(),
+ gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ KNOWN_AVAILABLE);
for (const auto &gridDesc : gridsNeeded) {
curHasGrids = true;
curSetOfGrids.insert(gridDesc.shortName);
@@ -11571,6 +11583,7 @@ CoordinateOperationFactory::Private::findOpsInRegistryDirect(
buildCRSIds(sourceCRS, context, sourceIds);
buildCRSIds(targetCRS, context, targetIds);
+ const auto gridAvailabilityUse = context.context->getGridAvailabilityUse();
for (const auto &idSrc : sourceIds) {
const auto &srcAuthName = idSrc.first;
const auto &srcCode = idSrc.second;
@@ -11590,9 +11603,16 @@ CoordinateOperationFactory::Private::findOpsInRegistryDirect(
tmpAuthFactory->createFromCoordinateReferenceSystemCodes(
srcAuthName, srcCode, targetAuthName, targetCode,
context.context->getUsePROJAlternativeGridNames(),
- context.context->getGridAvailabilityUse() ==
+ gridAvailabilityUse ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::
+ DISCARD_OPERATION_IF_MISSING_GRID ||
+ gridAvailabilityUse ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::KNOWN_AVAILABLE,
+ gridAvailabilityUse ==
CoordinateOperationContext::GridAvailabilityUse::
- DISCARD_OPERATION_IF_MISSING_GRID,
+ KNOWN_AVAILABLE,
context.context->getDiscardSuperseded(), true, false,
context.extent1, context.extent2);
res.insert(res.end(), resTmp.begin(), resTmp.end());
@@ -11635,6 +11655,7 @@ CoordinateOperationFactory::Private::findOpsInRegistryDirectTo(
std::list<std::pair<std::string, std::string>> ids;
buildCRSIds(targetCRS, context, ids);
+ const auto gridAvailabilityUse = context.context->getGridAvailabilityUse();
for (const auto &id : ids) {
const auto &targetAuthName = id.first;
const auto &targetCode = id.second;
@@ -11648,9 +11669,15 @@ CoordinateOperationFactory::Private::findOpsInRegistryDirectTo(
auto res = tmpAuthFactory->createFromCoordinateReferenceSystemCodes(
std::string(), std::string(), targetAuthName, targetCode,
context.context->getUsePROJAlternativeGridNames(),
- context.context->getGridAvailabilityUse() ==
- CoordinateOperationContext::GridAvailabilityUse::
- DISCARD_OPERATION_IF_MISSING_GRID,
+
+ gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ DISCARD_OPERATION_IF_MISSING_GRID ||
+ gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ KNOWN_AVAILABLE,
+ gridAvailabilityUse == CoordinateOperationContext::
+ GridAvailabilityUse::KNOWN_AVAILABLE,
context.context->getDiscardSuperseded(), true, true,
context.extent1, context.extent2);
if (!res.empty()) {
@@ -11698,6 +11725,7 @@ CoordinateOperationFactory::Private::findsOpsInRegistryWithIntermediate(
buildCRSIds(sourceCRS, context, sourceIds);
buildCRSIds(targetCRS, context, targetIds);
+ const auto gridAvailabilityUse = context.context->getGridAvailabilityUse();
for (const auto &idSrc : sourceIds) {
const auto &srcAuthName = idSrc.first;
const auto &srcCode = idSrc.second;
@@ -11717,21 +11745,28 @@ CoordinateOperationFactory::Private::findsOpsInRegistryWithIntermediate(
std::vector<CoordinateOperationNNPtr> res;
if (useCreateBetweenGeodeticCRSWithDatumBasedIntermediates) {
- res = tmpAuthFactory
- ->createBetweenGeodeticCRSWithDatumBasedIntermediates(
- sourceCRS, srcAuthName, srcCode, targetCRS,
- targetAuthName, targetCode,
- context.context->getUsePROJAlternativeGridNames(),
- context.context->getGridAvailabilityUse() ==
- CoordinateOperationContext::
- GridAvailabilityUse::
- DISCARD_OPERATION_IF_MISSING_GRID,
- context.context->getDiscardSuperseded(),
- authFactory->getAuthority() != "any" &&
- authorities.size() > 1
- ? authorities
- : std::vector<std::string>(),
- context.extent1, context.extent2);
+ res =
+ tmpAuthFactory
+ ->createBetweenGeodeticCRSWithDatumBasedIntermediates(
+ sourceCRS, srcAuthName, srcCode, targetCRS,
+ targetAuthName, targetCode,
+ context.context->getUsePROJAlternativeGridNames(),
+ gridAvailabilityUse ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::
+ DISCARD_OPERATION_IF_MISSING_GRID ||
+ gridAvailabilityUse ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::KNOWN_AVAILABLE,
+ gridAvailabilityUse ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::KNOWN_AVAILABLE,
+ context.context->getDiscardSuperseded(),
+ authFactory->getAuthority() != "any" &&
+ authorities.size() > 1
+ ? authorities
+ : std::vector<std::string>(),
+ context.extent1, context.extent2);
} else {
io::AuthorityFactory::ObjectType intermediateObjectType =
io::AuthorityFactory::ObjectType::CRS;
@@ -11749,9 +11784,15 @@ CoordinateOperationFactory::Private::findsOpsInRegistryWithIntermediate(
res = tmpAuthFactory->createFromCRSCodesWithIntermediates(
srcAuthName, srcCode, targetAuthName, targetCode,
context.context->getUsePROJAlternativeGridNames(),
- context.context->getGridAvailabilityUse() ==
+ gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ DISCARD_OPERATION_IF_MISSING_GRID ||
+ gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ KNOWN_AVAILABLE,
+ gridAvailabilityUse ==
CoordinateOperationContext::GridAvailabilityUse::
- DISCARD_OPERATION_IF_MISSING_GRID,
+ KNOWN_AVAILABLE,
context.context->getDiscardSuperseded(),
context.context->getIntermediateCRS(),
intermediateObjectType,
@@ -14167,15 +14208,21 @@ void CoordinateOperationFactory::Private::createOperationsCompoundToGeog(
componentsSrc[1],
targetCRS->promoteTo3D(std::string(), dbContext), context);
bool foundRegisteredTransformWithAllGridsAvailable = false;
+ const auto gridAvailabilityUse =
+ context.context->getGridAvailabilityUse();
const bool ignoreMissingGrids =
- context.context->getGridAvailabilityUse() ==
+ gridAvailabilityUse ==
CoordinateOperationContext::GridAvailabilityUse::
IGNORE_GRID_AVAILABILITY;
for (const auto &op : verticalTransforms) {
if (hasIdentifiers(op) && dbContext) {
bool missingGrid = false;
if (!ignoreMissingGrids) {
- const auto gridsNeeded = op->gridsNeeded(dbContext);
+ const auto gridsNeeded = op->gridsNeeded(
+ dbContext,
+ gridAvailabilityUse ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::KNOWN_AVAILABLE);
for (const auto &gridDesc : gridsNeeded) {
if (!gridDesc.available) {
missingGrid = true;
@@ -14204,7 +14251,11 @@ void CoordinateOperationFactory::Private::createOperationsCompoundToGeog(
if (hasIdentifiers(op) && dbContext) {
bool missingGrid = false;
if (!ignoreMissingGrids) {
- const auto gridsNeeded = op->gridsNeeded(dbContext);
+ const auto gridsNeeded = op->gridsNeeded(
+ dbContext,
+ gridAvailabilityUse ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::KNOWN_AVAILABLE);
for (const auto &gridDesc : gridsNeeded) {
if (!gridDesc.available) {
missingGrid = true;
@@ -15034,8 +15085,9 @@ CoordinateOperationNNPtr PROJBasedOperation::_shallowClone() const {
// ---------------------------------------------------------------------------
-std::set<GridDescription> PROJBasedOperation::gridsNeeded(
- const io::DatabaseContextPtr &databaseContext) const {
+std::set<GridDescription>
+PROJBasedOperation::gridsNeeded(const io::DatabaseContextPtr &databaseContext,
+ bool considerKnownGridsAsAvailable) const {
std::set<GridDescription> res;
try {
@@ -15048,7 +15100,8 @@ std::set<GridDescription> PROJBasedOperation::gridsNeeded(
desc.shortName = shortName;
if (databaseContext) {
databaseContext->lookForGridInfo(
- desc.shortName, desc.fullName, desc.packageName, desc.url,
+ desc.shortName, considerKnownGridsAsAvailable,
+ desc.fullName, desc.packageName, desc.url,
desc.directDownload, desc.openLicense, desc.available);
}
res.insert(desc);
diff --git a/src/iso19111/factory.cpp b/src/iso19111/factory.cpp
index 57850303..dae8680c 100644
--- a/src/iso19111/factory.cpp
+++ b/src/iso19111/factory.cpp
@@ -1018,14 +1018,14 @@ bool DatabaseContext::lookForGridAlternative(const std::string &officialName,
// ---------------------------------------------------------------------------
-bool DatabaseContext::lookForGridInfo(const std::string &projFilename,
- std::string &fullFilename,
- std::string &packageName,
- std::string &url, bool &directDownload,
- bool &openLicense,
- bool &gridAvailable) const {
+bool DatabaseContext::lookForGridInfo(
+ const std::string &projFilename, bool considerKnownGridsAsAvailable,
+ std::string &fullFilename, std::string &packageName, std::string &url,
+ bool &directDownload, bool &openLicense, bool &gridAvailable) const {
Private::GridInfoCache info;
- if (d->getGridInfoFromCache(projFilename, info)) {
+ const std::string key(projFilename +
+ (considerKnownGridsAsAvailable ? "true" : "false"));
+ if (d->getGridInfoFromCache(key, info)) {
fullFilename = info.fullFilename;
packageName = info.packageName;
url = info.url;
@@ -1041,16 +1041,20 @@ bool DatabaseContext::lookForGridInfo(const std::string &projFilename,
openLicense = false;
directDownload = false;
- fullFilename.resize(2048);
- if (d->pjCtxt() == nullptr) {
- d->setPjCtxt(pj_get_default_ctx());
+ if (considerKnownGridsAsAvailable) {
+ fullFilename = projFilename;
+ } else {
+ fullFilename.resize(2048);
+ if (d->pjCtxt() == nullptr) {
+ d->setPjCtxt(pj_get_default_ctx());
+ }
+ int errno_before = proj_context_errno(d->pjCtxt());
+ gridAvailable =
+ pj_find_file(d->pjCtxt(), projFilename.c_str(), &fullFilename[0],
+ fullFilename.size() - 1) != 0;
+ proj_context_errno_set(d->pjCtxt(), errno_before);
+ fullFilename.resize(strlen(fullFilename.c_str()));
}
- int errno_before = proj_context_errno(d->pjCtxt());
- gridAvailable =
- pj_find_file(d->pjCtxt(), projFilename.c_str(), &fullFilename[0],
- fullFilename.size() - 1) != 0;
- proj_context_errno_set(d->pjCtxt(), errno_before);
- fullFilename.resize(strlen(fullFilename.c_str()));
auto res =
d->run("SELECT "
@@ -1074,6 +1078,10 @@ bool DatabaseContext::lookForGridInfo(const std::string &projFilename,
openLicense = (row[3].empty() ? row[4] : row[3]) == "1";
directDownload = (row[5].empty() ? row[6] : row[5]) == "1";
+ if (considerKnownGridsAsAvailable && !packageName.empty()) {
+ gridAvailable = true;
+ }
+
info.fullFilename = fullFilename;
info.packageName = packageName;
info.url = url;
@@ -1082,7 +1090,7 @@ bool DatabaseContext::lookForGridInfo(const std::string &projFilename,
}
info.gridAvailable = gridAvailable;
info.found = ret;
- d->cache(projFilename, info);
+ d->cache(key, info);
return ret;
}
@@ -1264,8 +1272,8 @@ struct AuthorityFactory::Private {
return AuthorityFactory::create(context_, auth_name);
}
- bool
- rejectOpDueToMissingGrid(const operation::CoordinateOperationNNPtr &op);
+ bool rejectOpDueToMissingGrid(const operation::CoordinateOperationNNPtr &op,
+ bool considerKnownGridsAsAvailable);
UnitOfMeasure createUnitOfMeasure(const std::string &auth_name,
const std::string &code);
@@ -1392,8 +1400,10 @@ util::PropertyMap AuthorityFactory::Private::createProperties(
// ---------------------------------------------------------------------------
bool AuthorityFactory::Private::rejectOpDueToMissingGrid(
- const operation::CoordinateOperationNNPtr &op) {
- for (const auto &gridDesc : op->gridsNeeded(context())) {
+ const operation::CoordinateOperationNNPtr &op,
+ bool considerKnownGridsAsAvailable) {
+ for (const auto &gridDesc :
+ op->gridsNeeded(context(), considerKnownGridsAsAvailable)) {
if (!gridDesc.available) {
return true;
}
@@ -3381,7 +3391,7 @@ AuthorityFactory::createFromCoordinateReferenceSystemCodes(
const std::string &sourceCRSCode, const std::string &targetCRSCode) const {
return createFromCoordinateReferenceSystemCodes(
d->authority(), sourceCRSCode, d->authority(), targetCRSCode, false,
- false, false);
+ false, false, false);
}
// ---------------------------------------------------------------------------
@@ -3410,6 +3420,8 @@ AuthorityFactory::createFromCoordinateReferenceSystemCodes(
* should be substituted to the official grid names.
* @param discardIfMissingGrid Whether coordinate operations that reference
* missing grids should be removed from the result set.
+ * @param considerKnownGridsAsAvailable Whether known grids should be considered
+ * as available (typically when network is enabled).
* @param discardSuperseded Whether cordinate operations that are superseded
* (but not deprecated) should be removed from the result set.
* @param tryReverseOrder whether to search in the reverse order too (and thus
@@ -3430,8 +3442,8 @@ AuthorityFactory::createFromCoordinateReferenceSystemCodes(
const std::string &sourceCRSAuthName, const std::string &sourceCRSCode,
const std::string &targetCRSAuthName, const std::string &targetCRSCode,
bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
- bool discardSuperseded, bool tryReverseOrder,
- bool reportOnlyIntersectingTransformations,
+ bool considerKnownGridsAsAvailable, bool discardSuperseded,
+ bool tryReverseOrder, bool reportOnlyIntersectingTransformations,
const metadata::ExtentPtr &intersectingExtent1,
const metadata::ExtentPtr &intersectingExtent2) const {
@@ -3442,6 +3454,7 @@ AuthorityFactory::createFromCoordinateReferenceSystemCodes(
cacheKey += targetCRSCode;
cacheKey += (usePROJAlternativeGridNames ? '1' : '0');
cacheKey += (discardIfMissingGrid ? '1' : '0');
+ cacheKey += (considerKnownGridsAsAvailable ? '1' : '0');
cacheKey += (discardSuperseded ? '1' : '0');
cacheKey += (tryReverseOrder ? '1' : '0');
cacheKey += (reportOnlyIntersectingTransformations ? '1' : '0');
@@ -3680,7 +3693,8 @@ AuthorityFactory::createFromCoordinateReferenceSystemCodes(
target_crs_code != targetCRSCode))) {
op = op->inverse();
}
- if (!discardIfMissingGrid || !d->rejectOpDueToMissingGrid(op)) {
+ if (!discardIfMissingGrid ||
+ !d->rejectOpDueToMissingGrid(op, considerKnownGridsAsAvailable)) {
list.emplace_back(op);
}
}
@@ -3745,6 +3759,8 @@ static bool useIrrelevantPivot(const operation::CoordinateOperationNNPtr &op,
* should be substituted to the official grid names.
* @param discardIfMissingGrid Whether coordinate operations that reference
* missing grids should be removed from the result set.
+ * @param considerKnownGridsAsAvailable Whether known grids should be considered
+ * as available (typically when network is enabled).
* @param discardSuperseded Whether cordinate operations that are superseded
* (but not deprecated) should be removed from the result set.
* @param intermediateCRSAuthCodes List of (auth_name, code) of CRS that can be
@@ -3773,7 +3789,7 @@ AuthorityFactory::createFromCRSCodesWithIntermediates(
const std::string &sourceCRSAuthName, const std::string &sourceCRSCode,
const std::string &targetCRSAuthName, const std::string &targetCRSCode,
bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
- bool discardSuperseded,
+ bool considerKnownGridsAsAvailable, bool discardSuperseded,
const std::vector<std::pair<std::string, std::string>>
&intermediateCRSAuthCodes,
ObjectType allowedIntermediateObjectType,
@@ -4221,7 +4237,8 @@ AuthorityFactory::createFromCRSCodesWithIntermediates(
std::vector<operation::CoordinateOperationNNPtr> list;
for (const auto &op : listTmp) {
- if (!discardIfMissingGrid || !d->rejectOpDueToMissingGrid(op)) {
+ if (!discardIfMissingGrid ||
+ !d->rejectOpDueToMissingGrid(op, considerKnownGridsAsAvailable)) {
list.emplace_back(op);
}
}
@@ -4239,7 +4256,8 @@ AuthorityFactory::createBetweenGeodeticCRSWithDatumBasedIntermediates(
const std::string &sourceCRSCode, const crs::CRSNNPtr &targetCRS,
const std::string &targetCRSAuthName, const std::string &targetCRSCode,
bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
- bool discardSuperseded, const std::vector<std::string> &allowedAuthorities,
+ bool considerKnownGridsAsAvailable, bool discardSuperseded,
+ const std::vector<std::string> &allowedAuthorities,
const metadata::ExtentPtr &intersectingExtent1,
const metadata::ExtentPtr &intersectingExtent2) const {
@@ -4822,7 +4840,8 @@ AuthorityFactory::createBetweenGeodeticCRSWithDatumBasedIntermediates(
std::vector<operation::CoordinateOperationNNPtr> list;
for (const auto &op : listTmp) {
- if (!discardIfMissingGrid || !d->rejectOpDueToMissingGrid(op)) {
+ if (!discardIfMissingGrid ||
+ !d->rejectOpDueToMissingGrid(op, considerKnownGridsAsAvailable)) {
list.emplace_back(op);
}
}
diff --git a/src/lib_proj.cmake b/src/lib_proj.cmake
index f6112aef..12dcb366 100644
--- a/src/lib_proj.cmake
+++ b/src/lib_proj.cmake
@@ -198,6 +198,7 @@ set(SRC_LIBPROJ_TRANSFORMATIONS
transformations/horner.cpp
transformations/molodensky.cpp
transformations/vgridshift.cpp
+ transformations/xyzgridshift.cpp
)
set(SRC_LIBPROJ_ISO19111
@@ -281,6 +282,8 @@ set(SRC_LIBPROJ_CORE
tracing.cpp
grids.hpp
grids.cpp
+ filemanager.hpp
+ filemanager.cpp
${CMAKE_CURRENT_BINARY_DIR}/proj_config.h
)
@@ -438,6 +441,16 @@ endif()
include_directories(${SQLITE3_INCLUDE_DIR})
target_link_libraries(${PROJ_CORE_TARGET} ${SQLITE3_LIBRARY})
+if(NOT DISABLE_TIFF_IS_STRONGLY_DISCOURAGED)
+ include_directories(${TIFF_INCLUDE_DIR})
+ target_link_libraries(${PROJ_CORE_TARGET} ${TIFF_LIBRARY})
+endif()
+
+if(CURL_FOUND)
+ include_directories(${CURL_INCLUDE_DIR})
+ target_link_libraries(${PROJ_CORE_TARGET} ${CURL_LIBRARY})
+endif()
+
if(MSVC)
target_compile_definitions(${PROJ_CORE_TARGET}
PRIVATE PROJ_MSVC_DLL_EXPORT=1)
diff --git a/src/malloc.cpp b/src/malloc.cpp
index 3fd3699f..1c539b6b 100644
--- a/src/malloc.cpp
+++ b/src/malloc.cpp
@@ -49,6 +49,7 @@
#include "proj.h"
#include "proj_internal.h"
#include "grids.hpp"
+#include "filemanager.hpp"
using namespace NS_PROJ;
@@ -262,4 +263,5 @@ void proj_cleanup() {
/*****************************************************************************/
pj_clear_initcache();
pj_deallocate_grids();
+ FileManager::clearCache();
}
diff --git a/src/open_lib.cpp b/src/open_lib.cpp
index 24c31033..cde5be7b 100644
--- a/src/open_lib.cpp
+++ b/src/open_lib.cpp
@@ -44,6 +44,7 @@
#include "proj/internal/internal.hpp"
#include "proj_internal.h"
+#include "filemanager.hpp"
static const char * proj_lib_name =
#ifdef PROJ_LIB
@@ -52,6 +53,8 @@ PROJ_LIB;
nullptr;
#endif
+using namespace NS_PROJ::internal;
+
/************************************************************************/
/* pj_set_finder() */
/************************************************************************/
@@ -197,24 +200,39 @@ static const char *get_path_from_win32_projlib(const char *name, std::string& ou
#endif
/************************************************************************/
-/* pj_open_lib_ex() */
+/* pj_open_lib_internal() */
/************************************************************************/
-static PAFile
-pj_open_lib_ex(projCtx ctx, const char *name, const char *mode,
- char* out_full_filename, size_t out_full_filename_size) {
- try {
- std::string fname;
- const char *sysname = nullptr;
- PAFile fid = nullptr;
#ifdef WIN32
- static const char dir_chars[] = "/\\";
- const char dirSeparator = ';';
+static const char dir_chars[] = "/\\";
+static const char dirSeparator = ';';
#else
- static const char dir_chars[] = "/";
- const char dirSeparator = ':';
+static const char dir_chars[] = "/";
+static const char dirSeparator = ':';
#endif
+static bool is_tilde_slash(const char* name)
+{
+ return *name == '~' && strchr(dir_chars,name[1]);
+}
+
+static bool is_rel_or_absolute_filename(const char *name)
+{
+ return strchr(dir_chars,*name)
+ || (*name == '.' && strchr(dir_chars,name[1]))
+ || (!strncmp(name, "..", 2) && strchr(dir_chars,name[2]))
+ || (name[0] != '\0' && name[1] == ':' && strchr(dir_chars,name[2]));
+}
+
+static void*
+pj_open_lib_internal(projCtx ctx, const char *name, const char *mode,
+ void* (*open_file)(projCtx, const char*, const char*),
+ char* out_full_filename, size_t out_full_filename_size) {
+ try {
+ std::string fname;
+ const char *sysname = nullptr;
+ void* fid = nullptr;
+
if( ctx == nullptr ) {
ctx = pj_get_default_ctx();
}
@@ -223,7 +241,7 @@ pj_open_lib_ex(projCtx ctx, const char *name, const char *mode,
out_full_filename[0] = '\0';
/* check if ~/name */
- if (*name == '~' && strchr(dir_chars,name[1]) )
+ if (is_tilde_slash(name))
if ((sysname = getenv("HOME")) != nullptr) {
fname = sysname;
fname += DIR_CHAR;
@@ -232,11 +250,10 @@ pj_open_lib_ex(projCtx ctx, const char *name, const char *mode,
} else
return nullptr;
- /* or fixed path: /name, ./name or ../name */
- else if (strchr(dir_chars,*name)
- || (*name == '.' && strchr(dir_chars,name[1]))
- || (!strncmp(name, "..", 2) && strchr(dir_chars,name[2]))
- || (name[0] != '\0' && name[1] == ':' && strchr(dir_chars,name[2])) )
+ /* or fixed path: /name, ./name or ../name or http[s]:// */
+ else if (is_rel_or_absolute_filename(name)
+ || starts_with(name, "http://")
+ || starts_with(name, "https://"))
sysname = name;
/* or try to use application provided file finder */
@@ -254,7 +271,7 @@ pj_open_lib_ex(projCtx ctx, const char *name, const char *mode,
fname += DIR_CHAR;
fname += name;
sysname = fname.c_str();
- fid = pj_ctx_fopen(ctx, sysname, mode);
+ fid = open_file(ctx, sysname, mode);
} catch( const std::exception& )
{
}
@@ -270,7 +287,7 @@ pj_open_lib_ex(projCtx ctx, const char *name, const char *mode,
fname += DIR_CHAR;
fname += name;
sysname = fname.c_str();
- fid = pj_ctx_fopen(ctx, sysname, mode);
+ fid = open_file(ctx, sysname, mode);
if( fid )
break;
}
@@ -290,7 +307,7 @@ pj_open_lib_ex(projCtx ctx, const char *name, const char *mode,
}
assert(sysname); // to make Coverity Scan happy
- if ( fid != nullptr || (fid = pj_ctx_fopen(ctx, sysname, mode)) != nullptr)
+ if ( fid != nullptr || (fid = open_file(ctx, sysname, mode)) != nullptr)
{
if( out_full_filename != nullptr && out_full_filename_size > 0 )
{
@@ -322,18 +339,79 @@ pj_open_lib_ex(projCtx ctx, const char *name, const char *mode,
}
/************************************************************************/
+/* pj_open_file_with_manager() */
+/************************************************************************/
+
+static void* pj_open_file_with_manager(projCtx ctx, const char *name,
+ const char * /* mode */)
+{
+ return NS_PROJ::FileManager::open(ctx, name).release();
+}
+
+/************************************************************************/
+/* FileManager::open_resource_file() */
+/************************************************************************/
+
+std::unique_ptr<NS_PROJ::File> NS_PROJ::FileManager::open_resource_file(
+ projCtx ctx, const char *name)
+{
+ auto file = std::unique_ptr<NS_PROJ::File>(
+ reinterpret_cast<NS_PROJ::File*>(
+ pj_open_lib_internal(ctx, name, "rb",
+ pj_open_file_with_manager,
+ nullptr, 0)));
+ if( file == nullptr &&
+ !is_tilde_slash(name) &&
+ !is_rel_or_absolute_filename(name) &&
+ !starts_with(name, "http://") &&
+ !starts_with(name, "https://") &&
+ pj_context_is_network_enabled(ctx) ) {
+ std::string remote_file(pj_context_get_url_endpoint(ctx));
+ if( !remote_file.empty() ) {
+ if( remote_file.back() != '/' ) {
+ remote_file += '/';
+ }
+ remote_file += name;
+ auto pos = remote_file.rfind('.');
+ if( pos + 4 == remote_file.size() ) {
+ remote_file = remote_file.substr(0, pos) + ".tif";
+ file = open(ctx, remote_file.c_str());
+ if( file ) {
+ pj_log( ctx, PJ_LOG_DEBUG_MAJOR,
+ "Using %s", remote_file.c_str() );
+ pj_ctx_set_errno( ctx, 0 );
+ }
+ }
+ }
+ }
+ return file;
+}
+
+/************************************************************************/
/* pj_open_lib() */
/************************************************************************/
+#ifndef REMOVE_LEGACY_SUPPORT
+
+// Used by following legacy function
+static void* pj_ctx_fopen_adapter(projCtx ctx, const char *name, const char *mode)
+{
+ return pj_ctx_fopen(ctx, name, mode);
+}
+
+// Legacy function
PAFile
pj_open_lib(projCtx ctx, const char *name, const char *mode) {
- return pj_open_lib_ex(ctx, name, mode, nullptr, 0);
+ return (PAFile)pj_open_lib_internal(ctx, name, mode, pj_ctx_fopen_adapter, nullptr, 0);
}
+#endif // REMOVE_LEGACY_SUPPORT
+
/************************************************************************/
/* pj_find_file() */
/************************************************************************/
+
/** Returns the full filename corresponding to a proj resource file specified
* as a short filename.
*
@@ -348,12 +426,103 @@ pj_open_lib(projCtx ctx, const char *name, const char *mode) {
int pj_find_file(projCtx ctx, const char *short_filename,
char* out_full_filename, size_t out_full_filename_size)
{
- PAFile f = pj_open_lib_ex(ctx, short_filename, "rb", out_full_filename,
- out_full_filename_size);
+ auto f = reinterpret_cast<NS_PROJ::File*>(
+ pj_open_lib_internal(ctx, short_filename, "rb",
+ pj_open_file_with_manager,
+ out_full_filename,
+ out_full_filename_size));
if( f != nullptr )
{
- pj_ctx_fclose(ctx, f);
+ delete f;
return 1;
}
return 0;
}
+
+/************************************************************************/
+/* pj_context_get_url_endpoint() */
+/************************************************************************/
+
+std::string pj_context_get_url_endpoint(PJ_CONTEXT* ctx)
+{
+ if( !ctx->endpoint.empty() ) {
+ return ctx->endpoint;
+ }
+ pj_load_ini(ctx);
+ return ctx->endpoint;
+}
+
+/************************************************************************/
+/* trim() */
+/************************************************************************/
+
+static std::string trim(const std::string& s) {
+ const auto first = s.find_first_not_of(' ');
+ const auto last = s.find_last_not_of(' ');
+ if( first == std::string::npos || last == std::string::npos ) {
+ return std::string();
+ }
+ return s.substr(first, last - first + 1);
+}
+
+/************************************************************************/
+/* pj_load_ini() */
+/************************************************************************/
+
+void pj_load_ini(projCtx ctx)
+{
+ if( ctx->iniFileLoaded )
+ return;
+
+ const char* endpoint_from_env = getenv("PROJ_NETWORK_ENDPOINT");
+ if( endpoint_from_env && endpoint_from_env[0] != '\0' ) {
+ ctx->endpoint = endpoint_from_env;
+ }
+
+ ctx->iniFileLoaded = true;
+ 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 )
+ return;
+ file->seek(0, SEEK_END);
+ const auto filesize = file->tell();
+ if( filesize == 0 || filesize > 100 * 1024U )
+ return;
+ file->seek(0, SEEK_SET);
+ std::string content;
+ content.resize(static_cast<size_t>(filesize));
+ const auto nread = file->read(&content[0], content.size());
+ if( nread != content.size() )
+ return;
+ content += '\n';
+ size_t pos = 0;
+ while( pos != std::string::npos ) {
+ const auto eol = content.find_first_of("\r\n", pos);
+ if( eol == std::string::npos ) {
+ break;
+ }
+
+ const auto equal = content.find('=', pos);
+ if( equal < eol )
+ {
+ const auto key = trim(content.substr(pos, equal-pos));
+ const auto value = trim(content.substr(equal + 1,
+ eol - (equal+1)));
+ if( ctx->endpoint.empty() && key == "cdn_endpoint" ) {
+ ctx->endpoint = value;
+ } else if ( key == "network" ) {
+ const char *enabled = getenv("PROJ_NETWORK");
+ if (enabled == nullptr || enabled[0] == '\0') {
+ ctx->networking.enabled = ci_equal(value, "ON") ||
+ ci_equal(value, "YES") ||
+ ci_equal(value, "TRUE");
+ }
+ }
+ }
+
+ pos = content.find_first_not_of("\r\n", eol);
+ }
+}
diff --git a/src/pipeline.cpp b/src/pipeline.cpp
index 96767143..f65dbfa0 100644
--- a/src/pipeline.cpp
+++ b/src/pipeline.cpp
@@ -155,7 +155,7 @@ static PJ_LPZ pipeline_reverse_3d (PJ_XYZ xyz, PJ *P);
static PJ_XY pipeline_forward (PJ_LP lp, PJ *P);
static PJ_LP pipeline_reverse (PJ_XY xy, PJ *P);
-void pj_pipeline_assign_context_to_steps( PJ* P, PJ_CONTEXT* ctx )
+static void pipeline_reassign_context( PJ* P, PJ_CONTEXT* ctx )
{
auto pipeline = static_cast<struct Pipeline*>(P->opaque);
for( auto& step: pipeline->steps )
@@ -413,7 +413,7 @@ PJ *OPERATION(pipeline,0) {
P->fwd = pipeline_forward;
P->inv = pipeline_reverse;
P->destructor = destructor;
- P->is_pipeline = 1;
+ P->reassign_context = pipeline_reassign_context;
/* Currently, the pipeline driver is a raw bit mover, enabling other operations */
/* to collaborate efficiently. All prep/fin stuff is done at the step levels. */
diff --git a/src/pj_list.h b/src/pj_list.h
index 0923bba8..9798a36b 100644
--- a/src/pj_list.h
+++ b/src/pj_list.h
@@ -170,3 +170,4 @@ PROJ_HEAD(weren, "Werenskiold I")
PROJ_HEAD(wink1, "Winkel I")
PROJ_HEAD(wink2, "Winkel II")
PROJ_HEAD(wintri, "Winkel Tripel")
+PROJ_HEAD(xyzgridshift, "XYZ grid shift")
diff --git a/src/proj.h b/src/proj.h
index fc309542..96b9c3f8 100644
--- a/src/proj.h
+++ b/src/proj.h
@@ -353,6 +353,85 @@ void PROJ_DLL proj_context_set_search_paths(PJ_CONTEXT *ctx, int count_paths, co
void PROJ_DLL proj_context_use_proj4_init_rules(PJ_CONTEXT *ctx, int enable);
int PROJ_DLL proj_context_get_use_proj4_init_rules(PJ_CONTEXT *ctx, int from_legacy_code_path);
+/*! @endcond */
+
+/** Opaque structure for PROJ. Implementations might cast it to their
+ * structure/class of choice. */
+typedef struct PROJ_NETWORK_HANDLE PROJ_NETWORK_HANDLE;
+
+/** Network access: open callback
+ *
+ * Should try to read the size_to_read first bytes at the specified offset of
+ * the file given by URL url,
+ * and write them to buffer. *out_size_read should be updated with the actual
+ * amount of bytes read (== size_to_read if the file is larger than size_to_read).
+ * During this read, the implementation should make sure to store the HTTP
+ * headers from the server response to be able to respond to
+ * proj_network_get_header_value_cbk_type callback.
+ *
+ * error_string_max_size should be the maximum size that can be written into
+ * the out_error_string buffer (including terminating nul character).
+ *
+ * @return a non-NULL opaque handle in case of success.
+ */
+typedef PROJ_NETWORK_HANDLE* (*proj_network_open_cbk_type)(
+ PJ_CONTEXT* ctx,
+ const char* url,
+ unsigned long long offset,
+ size_t size_to_read,
+ void* buffer,
+ size_t* out_size_read,
+ size_t error_string_max_size,
+ char* out_error_string,
+ void* user_data);
+
+/** Network access: close callback */
+typedef void (*proj_network_close_cbk_type)(PJ_CONTEXT* ctx,
+ PROJ_NETWORK_HANDLE* handle,
+ void* user_data);
+
+/** Network access: get HTTP headers */
+typedef const char* (*proj_network_get_header_value_cbk_type)(
+ PJ_CONTEXT* ctx,
+ PROJ_NETWORK_HANDLE* handle,
+ const char* header_name,
+ void* user_data);
+
+/** Network access: read range
+ *
+ * Read size_to_read bytes from handle, starting at offset, into
+ * buffer.
+ *
+ * error_string_max_size should be the maximum size that can be written into
+ * the out_error_string buffer (including terminating nul character).
+ *
+ * @return the number of bytes actually read (0 in case of error)
+ */
+typedef size_t (*proj_network_read_range_type)(
+ PJ_CONTEXT* ctx,
+ PROJ_NETWORK_HANDLE* handle,
+ unsigned long long offset,
+ size_t size_to_read,
+ void* buffer,
+ size_t error_string_max_size,
+ char* out_error_string,
+ void* user_data);
+
+int PROJ_DLL proj_context_set_network_callbacks(
+ PJ_CONTEXT* ctx,
+ proj_network_open_cbk_type open_cbk,
+ proj_network_close_cbk_type close_cbk,
+ proj_network_get_header_value_cbk_type get_header_value_cbk,
+ proj_network_read_range_type read_range_cbk,
+ void* user_data);
+
+int PROJ_DLL proj_context_set_enable_network(PJ_CONTEXT* ctx,
+ int enabled);
+
+void PROJ_DLL proj_context_set_url_endpoint(PJ_CONTEXT* ctx, const char* url);
+
+/*! @cond Doxygen_Suppress */
+
/* Manage the transformation definition object PJ */
PJ PROJ_DLL *proj_create (PJ_CONTEXT *ctx, const char *definition);
PJ PROJ_DLL *proj_create_argv (PJ_CONTEXT *ctx, int argc, char **argv);
@@ -624,6 +703,12 @@ typedef enum {
/** Ignore grid availability at all. Results will be presented as if
* all grids were available. */
PROJ_GRID_AVAILABILITY_IGNORED,
+
+ /** Results will be presented as if grids known to PROJ (that is
+ * registered in the grid_alternatives table of its database) were
+ * available. Used typically when networking is enabled.
+ */
+ PROJ_GRID_AVAILABILITY_KNOWN_AVAILABLE,
} PROJ_GRID_AVAILABILITY_USE;
/** \brief PROJ string version. */
diff --git a/src/proj_internal.h b/src/proj_internal.h
index 7d826414..12ada034 100644
--- a/src/proj_internal.h
+++ b/src/proj_internal.h
@@ -214,9 +214,6 @@ size_t pj_trim_argc (char *args);
char **pj_trim_argv (size_t argc, char *args);
char *pj_make_args (size_t argc, char **argv);
-/* Lowest level: Minimum support for fileapi */
-void proj_fileapi_set (PJ *P, void *fileapi);
-
typedef struct { double r, i; } COMPLEX;
/* Forward declarations and typedefs for stuff needed inside the PJ object */
@@ -346,6 +343,7 @@ struct PJconsts {
PJ_OPERATOR inv4d = nullptr;
PJ_DESTRUCTOR destructor = nullptr;
+ void (*reassign_context)(PJ*, projCtx_t *) = nullptr;
/*************************************************************************************
@@ -413,7 +411,6 @@ struct PJconsts {
int geoc = 0; /* Geocentric latitude flag */
int is_latlong = 0; /* proj=latlong ... not really a projection at all */
int is_geocent = 0; /* proj=geocent ... not really a projection at all */
- int is_pipeline = 0; /* 1 if PJ represents a pipeline */
int need_ellps = 0; /* 0 for operations that are purely cartesian */
int skip_fwd_prepare = 0;
int skip_fwd_finalize = 0;
@@ -664,17 +661,29 @@ struct FACTORS {
/* NOTE: Remember to update src/strerrno.cpp, src/apps/gie.cpp and transient_error in */
/* src/transform.cpp when adding new value */
+// Legacy
struct projFileAPI_t;
struct projCppContext;
+struct projNetworkCallbacksAndData
+{
+ bool enabled = false;
+ bool enabled_env_variable_checked = false; // whereas we have checked PROJ_NETWORK env variable
+ proj_network_open_cbk_type open = nullptr;
+ proj_network_close_cbk_type close = nullptr;
+ proj_network_get_header_value_cbk_type get_header_value = nullptr;
+ proj_network_read_range_type read_range = nullptr;
+ void* user_data = nullptr;
+};
+
/* proj thread context */
struct projCtx_t {
int last_errno = 0;
int debug_level = 0;
void (*logger)(void *, int, const char *) = nullptr;
void *logger_app_data = nullptr;
- struct projFileAPI_t *fileapi = nullptr;
+ struct projFileAPI_t *fileapi_legacy = nullptr; // for proj_api.h legacy API
struct projCppContext* cpp_context = nullptr; /* internal context for C++ code */
int use_proj4_init_rules = -1; /* -1 = unknown, 0 = no, 1 = yes */
int epsg_file_exists = -1; /* -1 = unknown, 0 = no, 1 = yes */
@@ -686,6 +695,12 @@ struct projCtx_t {
const char* (*file_finder) (PJ_CONTEXT *, const char*, void* user_data) = nullptr;
void* file_finder_user_data = nullptr;
+ projNetworkCallbacksAndData networking{};
+ bool defer_grid_opening = false; // set by pj_obj_create()
+
+ bool iniFileLoaded = false;
+ std::string endpoint{};
+
int projStringParserCreateFromPROJStringRecursionCounter = 0; // to avoid potential infinite recursion in PROJStringParser::createFromPROJString()
projCtx_t() = default;
@@ -812,7 +827,12 @@ std::string pj_double_quote_string_param_if_needed(const std::string& str);
PJ *pj_create_internal (PJ_CONTEXT *ctx, const char *definition);
PJ *pj_create_argv_internal (PJ_CONTEXT *ctx, int argc, char **argv);
-void pj_pipeline_assign_context_to_steps( PJ* P, PJ_CONTEXT* ctx );
+// For use by projinfo
+bool PROJ_DLL pj_context_is_network_enabled(PJ_CONTEXT* ctx);
+
+std::string pj_context_get_url_endpoint(PJ_CONTEXT* ctx);
+
+void pj_load_ini(PJ_CONTEXT* ctx);
/* classic public API */
#include "proj_api.h"
diff --git a/src/transformations/hgridshift.cpp b/src/transformations/hgridshift.cpp
index e9983df6..24da4dde 100644
--- a/src/transformations/hgridshift.cpp
+++ b/src/transformations/hgridshift.cpp
@@ -17,6 +17,7 @@ struct hgridshiftData {
double t_final = 0;
double t_epoch = 0;
ListOfHGrids grids{};
+ bool defer_grid_opening = false;
};
} // anonymous namespace
@@ -25,6 +26,14 @@ static PJ_XYZ forward_3d(PJ_LPZ lpz, PJ *P) {
PJ_COORD point = {{0,0,0,0}};
point.lpz = lpz;
+ if ( Q->defer_grid_opening ) {
+ Q->defer_grid_opening = false;
+ Q->grids = proj_hgrid_init(P, "grids");
+ if ( proj_errno(P) ) {
+ return proj_coord_error().xyz;
+ }
+ }
+
if (!Q->grids.empty()) {
/* Only try the gridshift if at least one grid is loaded,
* otherwise just pass the coordinate through unchanged. */
@@ -40,6 +49,14 @@ static PJ_LPZ reverse_3d(PJ_XYZ xyz, PJ *P) {
PJ_COORD point = {{0,0,0,0}};
point.xyz = xyz;
+ if ( Q->defer_grid_opening ) {
+ Q->defer_grid_opening = false;
+ Q->grids = proj_hgrid_init(P, "grids");
+ if ( proj_errno(P) ) {
+ return proj_coord_error().lpz;
+ }
+ }
+
if (!Q->grids.empty()) {
/* Only try the gridshift if at least one grid is loaded,
* otherwise just pass the coordinate through unchanged. */
@@ -94,10 +111,19 @@ static PJ *destructor (PJ *P, int errlev) {
return pj_default_destructor(P, errlev);
}
+static void reassign_context( PJ* P, PJ_CONTEXT* ctx )
+{
+ auto Q = (struct hgridshiftData *) P->opaque;
+ for( auto& grid: Q->grids ) {
+ grid->reassign_context(ctx);
+ }
+}
+
PJ *TRANSFORMATION(hgridshift,0) {
auto Q = new hgridshiftData;
P->opaque = (void *) Q;
P->destructor = destructor;
+ P->reassign_context = reassign_context;
P->fwd4d = forward_4d;
P->inv4d = reverse_4d;
@@ -114,9 +140,9 @@ PJ *TRANSFORMATION(hgridshift,0) {
return destructor (P, PJD_ERR_NO_ARGS);
}
- /* TODO: Refactor into shared function that can be used */
- /* by both vgridshift and hgridshift */
- if (pj_param(P->ctx, P->params, "tt_final").i) {
+ /* TODO: Refactor into shared function that can be used */
+ /* by both vgridshift and hgridshift */
+ if (pj_param(P->ctx, P->params, "tt_final").i) {
Q->t_final = pj_param (P->ctx, P->params, "dt_final").f;
if (Q->t_final == 0) {
/* a number wasn't passed to +t_final, let's see if it was "now" */
@@ -131,16 +157,21 @@ PJ *TRANSFORMATION(hgridshift,0) {
}
}
- if (pj_param(P->ctx, P->params, "tt_epoch").i)
+ if (pj_param(P->ctx, P->params, "tt_epoch").i)
Q->t_epoch = pj_param (P->ctx, P->params, "dt_epoch").f;
- Q->grids = proj_hgrid_init(P, "grids");
- /* Was gridlist compiled properly? */
- if ( proj_errno(P) ) {
- proj_log_error(P, "hgridshift: could not find required grid(s).");
- return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID);
+ if( P->ctx->defer_grid_opening ) {
+ Q->defer_grid_opening = true;
}
+ else {
+ Q->grids = proj_hgrid_init(P, "grids");
+ /* Was gridlist compiled properly? */
+ if ( proj_errno(P) ) {
+ proj_log_error(P, "hgridshift: could not find required grid(s).");
+ return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID);
+ }
+ }
return P;
}
diff --git a/src/transformations/vgridshift.cpp b/src/transformations/vgridshift.cpp
index b964f45b..3e7a015e 100644
--- a/src/transformations/vgridshift.cpp
+++ b/src/transformations/vgridshift.cpp
@@ -18,14 +18,51 @@ struct vgridshiftData {
double t_epoch = 0;
double forward_multiplier = 0;
ListOfVGrids grids{};
+ bool defer_grid_opening = false;
};
} // anonymous namespace
+static void deal_with_vertcon_gtx_hack(PJ *P)
+{
+ struct vgridshiftData *Q = (struct vgridshiftData *) P->opaque;
+ // The .gtx VERTCON files stored millimetres, but the .tif files
+ // are in metres.
+ if( Q->forward_multiplier != 0.001 ) {
+ return;
+ }
+ const char* gridname = pj_param(P->ctx, P->params, "sgrids").s;
+ if( !gridname ) {
+ return;
+ }
+ if( strcmp(gridname, "vertconw.gtx") != 0 &&
+ strcmp(gridname, "vertconc.gtx") != 0 &&
+ strcmp(gridname, "vertcone.gtx") != 0 ) {
+ return;
+ }
+ if( Q->grids.empty() ) {
+ return;
+ }
+ const auto& grids = Q->grids[0]->grids();
+ if( !grids.empty() &&
+ grids[0]->name().find(".tif") != std::string::npos ) {
+ Q->forward_multiplier = 1.0;
+ }
+}
+
static PJ_XYZ forward_3d(PJ_LPZ lpz, PJ *P) {
struct vgridshiftData *Q = (struct vgridshiftData *) P->opaque;
PJ_COORD point = {{0,0,0,0}};
point.lpz = lpz;
+ if ( Q->defer_grid_opening ) {
+ Q->defer_grid_opening = false;
+ Q->grids = proj_vgrid_init(P, "grids");
+ deal_with_vertcon_gtx_hack(P);
+ if ( proj_errno(P) ) {
+ return proj_coord_error().xyz;
+ }
+ }
+
if (!Q->grids.empty()) {
/* Only try the gridshift if at least one grid is loaded,
* otherwise just pass the coordinate through unchanged. */
@@ -41,6 +78,15 @@ static PJ_LPZ reverse_3d(PJ_XYZ xyz, PJ *P) {
PJ_COORD point = {{0,0,0,0}};
point.xyz = xyz;
+ if ( Q->defer_grid_opening ) {
+ Q->defer_grid_opening = false;
+ Q->grids = proj_vgrid_init(P, "grids");
+ deal_with_vertcon_gtx_hack(P);
+ if ( proj_errno(P) ) {
+ return proj_coord_error().lpz;
+ }
+ }
+
if (!Q->grids.empty()) {
/* Only try the gridshift if at least one grid is loaded,
* otherwise just pass the coordinate through unchanged. */
@@ -96,10 +142,20 @@ static PJ *destructor (PJ *P, int errlev) {
return pj_default_destructor(P, errlev);
}
+static void reassign_context( PJ* P, PJ_CONTEXT* ctx )
+{
+ auto Q = (struct vgridshiftData *) P->opaque;
+ for( auto& grid: Q->grids ) {
+ grid->reassign_context(ctx);
+ }
+}
+
+
PJ *TRANSFORMATION(vgridshift,0) {
auto Q = new vgridshiftData;
P->opaque = (void *) Q;
P->destructor = destructor;
+ P->reassign_context = reassign_context;
if (!pj_param(P->ctx, P->params, "tgrids").i) {
proj_log_error(P, "vgridshift: +grids parameter missing.");
@@ -132,13 +188,18 @@ PJ *TRANSFORMATION(vgridshift,0) {
Q->forward_multiplier = pj_param(P->ctx, P->params, "dmultiplier").f;
}
- /* Build gridlist. P->vgridlist_geoid can be empty if +grids only ask for optional grids. */
- Q->grids = proj_vgrid_init(P, "grids");
-
- /* Was gridlist compiled properly? */
- if ( proj_errno(P) ) {
- proj_log_error(P, "vgridshift: could not find required grid(s).");
- return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID);
+ if( P->ctx->defer_grid_opening ) {
+ Q->defer_grid_opening = true;
+ }
+ else {
+ /* Build gridlist. P->vgridlist_geoid can be empty if +grids only ask for optional grids. */
+ Q->grids = proj_vgrid_init(P, "grids");
+
+ /* Was gridlist compiled properly? */
+ if ( proj_errno(P) ) {
+ proj_log_error(P, "vgridshift: could not find required grid(s).");
+ return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID);
+ }
}
P->fwd4d = forward_4d;
diff --git a/src/transformations/xyzgridshift.cpp b/src/transformations/xyzgridshift.cpp
new file mode 100644
index 00000000..a76f3255
--- /dev/null
+++ b/src/transformations/xyzgridshift.cpp
@@ -0,0 +1,353 @@
+/******************************************************************************
+ * Project: PROJ
+ * Purpose: Geocentric translation using a grid
+ * Author: Even Rouault, <even.rouault at spatialys.com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2019, Even Rouault, <even.rouault at spatialys.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *****************************************************************************/
+
+#define PJ_LIB__
+
+#include <errno.h>
+#include <string.h>
+#include <stddef.h>
+#include <time.h>
+
+#include "proj_internal.h"
+#include "grids.hpp"
+
+#include <algorithm>
+
+PROJ_HEAD(xyzgridshift, "Geocentric grid shift");
+
+using namespace NS_PROJ;
+
+namespace { // anonymous namespace
+struct xyzgridshiftData {
+ PJ *cart = nullptr;
+ bool grid_ref_is_input = true;
+ ListOfGenericGrids grids{};
+ bool defer_grid_opening = false;
+ double multiplier = 1.0;
+};
+} // anonymous namespace
+
+
+// ---------------------------------------------------------------------------
+
+static const GenericShiftGrid* findGrid(const ListOfGenericGrids& grids,
+ const PJ_LP& input)
+{
+ for( const auto& gridset: grids )
+ {
+ auto grid = gridset->gridAt(input.lam, input.phi);
+ if( grid )
+ return grid;
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+static bool get_grid_values(PJ* P,
+ xyzgridshiftData* Q,
+ const PJ_LP& lp,
+ double& dx,
+ double& dy,
+ double& dz)
+{
+ if ( Q->defer_grid_opening ) {
+ Q->defer_grid_opening = false;
+ Q->grids = proj_generic_grid_init(P, "grids");
+ if ( proj_errno(P) ) {
+ return false;
+ }
+ }
+
+ auto grid = findGrid(Q->grids, lp);
+ if( !grid ) {
+ return false;
+ }
+ if( grid->isNullGrid() ) {
+ dx = 0;
+ dy = 0;
+ dz = 0;
+ return true;
+ }
+ const auto samplesPerPixel = grid->samplesPerPixel();
+ if( samplesPerPixel < 3 ) {
+ proj_log_error(P, "xyzgridshift: grid has not enough samples");
+ return false;
+ }
+ int sampleX = 0;
+ int sampleY = 1;
+ int sampleZ = 2;
+ for( int i = 0; i < samplesPerPixel; i++ )
+ {
+ const auto desc = grid->description(i);
+ if( desc == "x_translation") {
+ sampleX = i;
+ } else if( desc == "y_translation") {
+ sampleY = i;
+ } else if( desc == "z_translation") {
+ sampleZ = i;
+ }
+ }
+ const auto unit = grid->unit(sampleX);
+ if( !unit.empty() && unit != "metre" ) {
+ proj_log_error(P, "xyzgridshift: Only unit=metre currently handled");
+ return false;
+ }
+
+ const auto& extent = grid->extentAndRes();
+ double grid_x = (lp.lam - extent.westLon) / extent.resLon;
+ double grid_y = (lp.phi - extent.southLat) / extent.resLat;
+ int ix = static_cast<int>(grid_x);
+ int iy = static_cast<int>(grid_y);
+ int ix2 = std::min(ix + 1, grid->width() - 1);
+ int iy2 = std::min(iy + 1, grid->height() - 1);
+
+ float dx1, dy1, dz1;
+ if( !grid->valueAt(ix, iy, sampleX, dx1) ||
+ !grid->valueAt(ix, iy, sampleY, dy1) ||
+ !grid->valueAt(ix, iy, sampleZ, dz1) ) {
+ return false;
+ }
+
+ float dx2, dy2, dz2;
+ if( !grid->valueAt(ix2, iy, sampleX, dx2) ||
+ !grid->valueAt(ix2, iy, sampleY, dy2) ||
+ !grid->valueAt(ix2, iy, sampleZ, dz2) ) {
+ return false;
+ }
+
+ float dx3, dy3, dz3;
+ if( !grid->valueAt(ix, iy2, sampleX, dx3) ||
+ !grid->valueAt(ix, iy2, sampleY, dy3) ||
+ !grid->valueAt(ix, iy2, sampleZ, dz3) ) {
+ return false;
+ }
+
+ float dx4, dy4, dz4;
+ if( !grid->valueAt(ix2, iy2, sampleX, dx4) ||
+ !grid->valueAt(ix2, iy2, sampleY, dy4) ||
+ !grid->valueAt(ix2, iy2, sampleZ, dz4) ) {
+ return false;
+ }
+
+ double frct_lam = grid_x - ix;
+ double frct_phi = grid_y - iy;
+ double m10 = frct_lam;
+ double m11 = m10;
+ double m01 = 1. - frct_lam;
+ double m00 = m01;
+ m11 *= frct_phi;
+ m01 *= frct_phi;
+ frct_phi = 1. - frct_phi;
+ m00 *= frct_phi;
+ m10 *= frct_phi;
+ dx = (m00 * dx1 + m10 * dx2 + m01 * dx3 + m11 * dx4) * Q->multiplier;
+ dy = (m00 * dy1 + m10 * dy2 + m01 * dy3 + m11 * dy4) * Q->multiplier;
+ dz = (m00 * dz1 + m10 * dz2 + m01 * dz3 + m11 * dz4) * Q->multiplier;
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+#define SQUARE(x) ((x)*(x))
+
+// ---------------------------------------------------------------------------
+
+static PJ_COORD iterative_adjustment(PJ* P,
+ xyzgridshiftData* Q,
+ const PJ_COORD& pointInit,
+ double factor)
+{
+ PJ_COORD point = pointInit;
+ for(int i = 0; i < 10; i++) {
+ PJ_COORD geodetic;
+ geodetic.lpz = pj_inv3d(point.xyz, Q->cart);
+
+ double dx, dy, dz;
+ if( !get_grid_values(P, Q, geodetic.lp, dx, dy, dz) ) {
+ return proj_coord_error();
+ }
+
+ dx *= factor;
+ dy *= factor;
+ dz *= factor;
+
+ const double err = SQUARE((point.xyz.x - pointInit.xyz.x) - dx) +
+ SQUARE((point.xyz.y - pointInit.xyz.y) - dy) +
+ SQUARE((point.xyz.z - pointInit.xyz.z) - dz);
+
+ point.xyz.x = pointInit.xyz.x + dx;
+ point.xyz.y = pointInit.xyz.y + dy;
+ point.xyz.z = pointInit.xyz.z + dz;
+ if( err < 1e-10 ) {
+ break;
+ }
+ }
+ return point;
+}
+
+// ---------------------------------------------------------------------------
+
+static PJ_COORD direct_adjustment(PJ* P,
+ xyzgridshiftData* Q,
+ PJ_COORD point,
+ double factor)
+{
+ PJ_COORD geodetic;
+ geodetic.lpz = pj_inv3d(point.xyz, Q->cart);
+
+ double dx, dy, dz;
+ if( !get_grid_values(P, Q, geodetic.lp, dx, dy, dz) ) {
+ return proj_coord_error();
+ }
+ point.xyz.x += factor * dx;
+ point.xyz.y += factor * dy;
+ point.xyz.z += factor * dz;
+ return point;
+}
+
+// ---------------------------------------------------------------------------
+
+static PJ_XYZ forward_3d(PJ_LPZ lpz, PJ *P) {
+ auto Q = static_cast<xyzgridshiftData*>(P->opaque);
+ PJ_COORD point = {{0,0,0,0}};
+ point.lpz = lpz;
+
+ if( Q->grid_ref_is_input ) {
+ point = direct_adjustment(P, Q, point, 1.0);
+ }
+ else {
+ point = iterative_adjustment(P, Q, point, 1.0);
+ }
+
+ return point.xyz;
+}
+
+
+static PJ_LPZ reverse_3d(PJ_XYZ xyz, PJ *P) {
+ auto Q = static_cast<xyzgridshiftData*>(P->opaque);
+ PJ_COORD point = {{0,0,0,0}};
+ point.xyz = xyz;
+
+ if( Q->grid_ref_is_input ) {
+ point = iterative_adjustment(P, Q, point, -1.0);
+ }
+ else {
+ point = direct_adjustment(P, Q, point, -1.0);
+ }
+
+ return point.lpz;
+}
+
+static PJ *destructor (PJ *P, int errlev) {
+ if (nullptr==P)
+ return nullptr;
+
+ auto Q = static_cast<struct xyzgridshiftData*>(P->opaque);
+ if( Q )
+ {
+ if (Q->cart)
+ Q->cart->destructor (Q->cart, errlev);
+ delete Q;
+ }
+ P->opaque = nullptr;
+
+ return pj_default_destructor(P, errlev);
+}
+
+static void reassign_context( PJ* P, PJ_CONTEXT* ctx )
+{
+ auto Q = (struct xyzgridshiftData *) P->opaque;
+ for( auto& grid: Q->grids ) {
+ grid->reassign_context(ctx);
+ }
+}
+
+
+PJ *TRANSFORMATION(xyzgridshift,0) {
+ auto Q = new xyzgridshiftData;
+ P->opaque = (void *) Q;
+ P->destructor = destructor;
+ P->reassign_context = reassign_context;
+
+ P->fwd4d = nullptr;
+ P->inv4d = nullptr;
+ P->fwd3d = forward_3d;
+ P->inv3d = reverse_3d;
+ P->fwd = nullptr;
+ P->inv = nullptr;
+
+ P->left = PJ_IO_UNITS_CARTESIAN;
+ P->right = PJ_IO_UNITS_CARTESIAN;
+
+ // Pass a dummy ellipsoid definition that will be overridden just afterwards
+ Q->cart = proj_create(P->ctx, "+proj=cart +a=1");
+ if (Q->cart == nullptr)
+ return destructor(P, ENOMEM);
+
+ /* inherit ellipsoid definition from P to Q->cart */
+ pj_inherit_ellipsoid_def (P, Q->cart);
+
+ const char* grid_ref = pj_param (P->ctx, P->params, "sgrid_ref").s;
+ if( grid_ref ) {
+ if (strcmp(grid_ref, "input_crs") == 0 ) {
+ // default
+ } else if (strcmp(grid_ref, "output_crs") == 0 ) {
+ // Convention use for example for NTF->RGF93 grid that contains
+ // delta x,y,z from NTF to RGF93, but the grid itself is referenced
+ // in RGF93
+ Q->grid_ref_is_input = false;
+ } else {
+ proj_log_error(P, "xyzgridshift: unusupported value for grid_ref");
+ return destructor (P, PJD_ERR_NO_ARGS);
+ }
+ }
+
+ if (0==pj_param(P->ctx, P->params, "tgrids").i) {
+ proj_log_error(P, "xyzgridshift: +grids parameter missing.");
+ return destructor (P, PJD_ERR_NO_ARGS);
+ }
+
+ /* multiplier for delta x,y,z */
+ if (pj_param(P->ctx, P->params, "tmultiplier").i) {
+ Q->multiplier = pj_param(P->ctx, P->params, "dmultiplier").f;
+ }
+
+ if( P->ctx->defer_grid_opening ) {
+ Q->defer_grid_opening = true;
+ }
+ else {
+ Q->grids = proj_generic_grid_init(P, "grids");
+ /* Was gridlist compiled properly? */
+ if ( proj_errno(P) ) {
+ proj_log_error(P, "xyzgridshift: could not find required grid(s).");
+ return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID);
+ }
+ }
+
+ return P;
+}
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index ae721d46..a7aac755 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -10,6 +10,7 @@ proj_add_gie_test("GDA" "gie/GDA.gie")
proj_add_gie_test("4D-API-cs2cs-style" "gie/4D-API_cs2cs-style.gie")
proj_add_gie_test("DHDN_ETRS89" "gie/DHDN_ETRS89.gie")
proj_add_gie_test("Unitconvert" "gie/unitconvert.gie")
+proj_add_gie_test("geotiff_grids" "gie/geotiff_grids.gie")
# GIGS tests. Uncommented tests are expected to fail due to issues with
# various projections. Should be investigated further and fixed.
diff --git a/test/cli/ntv2_out.dist b/test/cli/ntv2_out.dist
index 9a97f9cf..650a69d8 100644
--- a/test/cli/ntv2_out.dist
+++ b/test/cli/ntv2_out.dist
@@ -9,3 +9,6 @@ Try with NTv2 and NTv1 together ... falls back to NTv1
99d00'00.000"W 65d00'00.000"N 0.0 99d0'1.5885"W 65d0'1.3482"N 0.000
111d00'00.000"W 46d00'00.000"N 0.0 111d0'3.1897"W 45d59'59.7489"N 0.000
111d00'00.000"W 47d30'00.000"N 0.0 111d0'2.7989"W 47d29'59.9896"N 0.000
+##############################################################
+Switching between NTv2 subgrids
+-112.5839956 49.4914451 0 -112.58307487 49.49145197 0.00000000
diff --git a/test/cli/test27 b/test/cli/test27
index bfc1cb0a..5825ed80 100755
--- a/test/cli/test27
+++ b/test/cli/test27
@@ -34,7 +34,7 @@ echo "Running ${0} using ${EXE}:"
echo "============================================"
OUT=proj_out27
-INIT_FILE=${PROJ_LIB}/nad27
+INIT_FILE=nad27
#
echo "doing tests into file ${OUT}, please wait"
#
diff --git a/test/cli/test83 b/test/cli/test83
index cfb1365e..8c1293d0 100755
--- a/test/cli/test83
+++ b/test/cli/test83
@@ -35,7 +35,7 @@ echo "Running ${0} using ${EXE}:"
echo "============================================"
OUT=proj_out83
-INIT_FILE=${PROJ_LIB}/nad83
+INIT_FILE=nad83
#
echo "doing tests into file ${OUT}, please wait"
#
diff --git a/test/cli/testdatumfile b/test/cli/testdatumfile
index 29a40876..9bd12ce4 100755
--- a/test/cli/testdatumfile
+++ b/test/cli/testdatumfile
@@ -27,7 +27,11 @@ echo "Running ${0} using ${EXE}:"
echo "============================================"
mkdir "dir with \" space"
-cp ${PROJ_LIB}/conus "dir with \" space/myconus"
+if test -f "${PROJ_LIB}/conus"; then
+ cp "${PROJ_LIB}/conus" "dir with \" space/myconus"
+else
+ cp "`dirname $0`/../../data/conus" "dir with \" space/myconus"
+fi
OUT=td_out
#EXE=../src/cs2cs
diff --git a/test/cli/testntv2 b/test/cli/testntv2
index 73371dbe..2a31304e 100755
--- a/test/cli/testntv2
+++ b/test/cli/testntv2
@@ -52,6 +52,14 @@ $EXE +proj=latlong +ellps=clrk66 +nadgrids=ntv2_0.gsb,ntv1_can.dat,conus \
111d00'00.000"W 46d00'00.000"N 0.0
111d00'00.000"W 47d30'00.000"N 0.0
EOF
+
+echo "##############################################################" >> ${OUT}
+echo Switching between NTv2 subgrids >> ${OUT}
+# Initial guess is in ALraymnd, going to parent CAwest afterwards
+$EXE +proj=latlong +datum=NAD83 +to +proj=latlong +ellps=clrk66 +nadgrids=ntv2_0.gsb -E -d 8 >>${OUT} <<EOF
+-112.5839956 49.4914451 0
+EOF
+
#
##############################################################################
# Done!
diff --git a/test/gie/4D-API_cs2cs-style.gie b/test/gie/4D-API_cs2cs-style.gie
index 8d541823..3e4b9d2c 100644
--- a/test/gie/4D-API_cs2cs-style.gie
+++ b/test/gie/4D-API_cs2cs-style.gie
@@ -442,7 +442,6 @@ Test bugfix of https://github.com/OSGeo/proj.4/issues/1002
-------------------------------------------------------------------------------
operation +proj=latlong +ellps=WGS84 +geoidgrids=tests/test_nodata.gtx
-------------------------------------------------------------------------------
-ignore pjd_err_failed_to_load_grid
accept 4.05 52.1 0
expect 4.05 52.1 -10
-------------------------------------------------------------------------------
@@ -452,7 +451,6 @@ Test null grid with vgridshift
-------------------------------------------------------------------------------
operation proj=vgridshift grids=tests/test_nodata.gtx,null ellps=GRS80
-------------------------------------------------------------------------------
-ignore pjd_err_failed_to_load_grid
accept 4.05 52.1 0
expect 4.05 52.1 -10
diff --git a/test/gie/Makefile.am b/test/gie/Makefile.am
index 44facd87..ff333a14 100644
--- a/test/gie/Makefile.am
+++ b/test/gie/Makefile.am
@@ -9,7 +9,8 @@ EXTRA_DIST = 4D-API_cs2cs-style.gie \
ellipsoid.gie \
more_builtins.gie \
unitconvert.gie \
- DHDN_ETRS89.gie
+ DHDN_ETRS89.gie \
+ geotiff_grids.gie
PROJ_LIB ?= ../../data
@@ -40,4 +41,7 @@ unitconvert: unitconvert.gie
DHDN_ETRS89: DHDN_ETRS89.gie
PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $<
-check-local: 4D-API-cs2cs-style GDA axisswap builtins deformation ellipsoid more_builtins unitconvert DHDN_ETRS89
+geotiff_grids: geotiff_grids.gie
+ PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $<
+
+check-local: 4D-API-cs2cs-style GDA axisswap builtins deformation ellipsoid more_builtins unitconvert DHDN_ETRS89 geotiff_grids
diff --git a/test/gie/geotiff_grids.gie b/test/gie/geotiff_grids.gie
new file mode 100644
index 00000000..920fcf28
--- /dev/null
+++ b/test/gie/geotiff_grids.gie
@@ -0,0 +1,306 @@
+
+-------------------------------------------------------------------------------
+===============================================================================
+Test GeoTIFF grids
+===============================================================================
+
+<gie>
+
+-------------------------------------------------------------------------------
+operation +proj=vgridshift +grids=tests/test_vgrid_pixelispoint.tif +multiplier=1
+-------------------------------------------------------------------------------
+accept 4.5 52.5 0
+expect 4.5 52.5 11.5
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+operation +proj=vgridshift +grids=tests/test_vgrid_pixelisarea.tif +multiplier=1
+-------------------------------------------------------------------------------
+accept 4.5 52.5 0
+expect 4.5 52.5 11.5
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+operation +proj=vgridshift +grids=tests/test_vgrid_deflate.tif +multiplier=1
+-------------------------------------------------------------------------------
+accept 4.5 52.5 0
+expect 4.5 52.5 11.5
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+operation +proj=vgridshift +grids=tests/test_vgrid_deflate_floatingpointpredictor.tif +multiplier=1
+-------------------------------------------------------------------------------
+accept 4.5 52.5 0
+expect 4.5 52.5 11.5
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+operation +proj=vgridshift +grids=tests/test_vgrid_uint16.tif +multiplier=1
+-------------------------------------------------------------------------------
+accept 4.5 52.5 0
+expect 4.5 52.5 11.5
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+operation +proj=vgridshift +grids=tests/test_vgrid_uint16_with_scale_offset.tif +multiplier=1
+-------------------------------------------------------------------------------
+accept 4.5 52.5 0
+expect 4.5 52.5 11.5
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+operation +proj=vgridshift +grids=tests/test_vgrid_int16.tif +multiplier=1
+-------------------------------------------------------------------------------
+accept 4.5 52.5 0
+expect 4.5 52.5 11.5
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+operation +proj=vgridshift +grids=tests/test_vgrid_int32.tif +multiplier=1
+-------------------------------------------------------------------------------
+accept 4.5 52.5 0
+expect 4.5 52.5 11.5
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+operation +proj=vgridshift +grids=tests/test_vgrid_uint32.tif +multiplier=1
+-------------------------------------------------------------------------------
+accept 4.5 52.5 0
+expect 4.5 52.5 11.5
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+operation +proj=vgridshift +grids=tests/test_vgrid_float64.tif +multiplier=1
+-------------------------------------------------------------------------------
+accept 4.5 52.5 0
+expect 4.5 52.5 11.5
+-------------------------------------------------------------------------------
+
+# The overview should be ignored
+-------------------------------------------------------------------------------
+operation +proj=vgridshift +grids=tests/test_vgrid_with_overview.tif +multiplier=1
+-------------------------------------------------------------------------------
+accept 4.5 52.5 0
+expect 4.5 52.5 11.5
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+operation +proj=vgridshift +grids=tests/test_vgrid_in_second_channel.tif +multiplier=1
+-------------------------------------------------------------------------------
+accept 4.5 52.5 0
+expect 4.5 52.5 11.5
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+operation +proj=vgridshift +grids=tests/test_vgrid_bigtiff.tif +multiplier=1
+-------------------------------------------------------------------------------
+accept 4.5 52.5 0
+expect 4.5 52.5 11.5
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+operation +proj=vgridshift +grids=tests/test_vgrid_bigendian.tif +multiplier=1
+-------------------------------------------------------------------------------
+accept 4.5 52.5 0
+expect 4.5 52.5 11.5
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+operation +proj=vgridshift +grids=tests/test_vgrid_bigendian_bigtiff.tif +multiplier=1
+-------------------------------------------------------------------------------
+accept 4.5 52.5 0
+expect 4.5 52.5 11.5
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+operation +proj=vgridshift +grids=tests/test_vgrid_bottomup_with_scale.tif +multiplier=1
+-------------------------------------------------------------------------------
+accept 4.5 52.5 0
+expect 4.5 52.5 11.5
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+operation +proj=vgridshift +grids=tests/test_vgrid_bottomup_with_matrix.tif +multiplier=1
+-------------------------------------------------------------------------------
+accept 4.5 52.5 0
+expect 4.5 52.5 11.5
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+operation +proj=vgridshift +grids=tests/test_vgrid_with_subgrid.tif +multiplier=1
+-------------------------------------------------------------------------------
+accept 4.5 52.5 0
+expect 4.5 52.5 11.5
+
+# In subgrid
+accept 5.5 53.5 0
+expect 5.5 53.5 110.0
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+operation +proj=vgridshift +grids=tests/test_vgrid_nodata.tif +multiplier=1
+-------------------------------------------------------------------------------
+accept 4.05 52.1 0
+expect 4.05 52.1 10
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+operation +proj=vgridshift +grids=tests/test_vgrid_invalid_channel_type.tif +multiplier=1
+-------------------------------------------------------------------------------
+expect failure errno failed_to_load_grid
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+operation +proj=vgridshift +grids=tests/test_vgrid_unsupported_byte.tif +multiplier=1
+-------------------------------------------------------------------------------
+expect failure errno failed_to_load_grid
+-------------------------------------------------------------------------------
+
+
+
+-------------------------------------------------------------------------------
+operation +proj=hgridshift +grids=tests/test_hgrid.tif
+-------------------------------------------------------------------------------
+tolerance 2 mm
+accept 4.5 52.5 0
+expect 5.875 55.375 0
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+operation +proj=hgridshift +grids=tests/test_hgrid_separate.tif
+-------------------------------------------------------------------------------
+tolerance 2 mm
+accept 4.5 52.5 0
+expect 5.875 55.375 0
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+operation +proj=hgridshift +grids=tests/test_hgrid_strip.tif
+-------------------------------------------------------------------------------
+tolerance 2 mm
+accept 4.5 52.5 0
+expect 5.875 55.375 0
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+operation +proj=hgridshift +grids=tests/test_hgrid_tiled.tif
+-------------------------------------------------------------------------------
+tolerance 2 mm
+accept 4.5 52.5 0
+expect 5.875 55.375 0
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+operation +proj=hgridshift +grids=tests/test_hgrid_tiled_separate.tif
+-------------------------------------------------------------------------------
+tolerance 2 mm
+accept 4.5 52.5 0
+expect 5.875 55.375 0
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+operation +proj=hgridshift +grids=tests/test_hgrid_positive_west.tif
+-------------------------------------------------------------------------------
+tolerance 2 mm
+accept 4.5 52.5 0
+expect 5.875 55.375 0
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+operation +proj=hgridshift +grids=tests/test_hgrid_lon_shift_first.tif
+-------------------------------------------------------------------------------
+tolerance 2 mm
+accept 4.5 52.5 0
+expect 5.875 55.375 0
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+operation +proj=hgridshift +grids=tests/test_hgrid_radian.tif
+-------------------------------------------------------------------------------
+tolerance 2 mm
+accept 4.5 52.5 0
+expect 5.875 55.375 0
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+operation +proj=hgridshift +grids=tests/test_hgrid_degree.tif
+-------------------------------------------------------------------------------
+tolerance 2 mm
+accept 4.5 52.5 0
+expect 5.875 55.375 0
+-------------------------------------------------------------------------------
+
+# The overview should be ignored
+-------------------------------------------------------------------------------
+operation +proj=hgridshift +grids=tests/test_hgrid_with_overview.tif
+-------------------------------------------------------------------------------
+tolerance 2 mm
+accept 4.5 52.5 0
+expect 5.875 55.375 0
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+operation +proj=hgridshift +grids=tests/test_hgrid_extra_ifd_with_other_info.tif
+-------------------------------------------------------------------------------
+tolerance 2 mm
+accept 4.5 52.5 0
+expect 5.875 55.375 0
+-------------------------------------------------------------------------------
+
+# Subset of NTv2_0.gsb
+-------------------------------------------------------------------------------
+operation +proj=hgridshift +grids=tests/test_hgrid_with_subgrid.tif
+-------------------------------------------------------------------------------
+# In subgrid ALbanff, of parent CAwest
+accept -115.5416667 51.1666667 0
+expect -115.5427092888 51.1666899972 0
+
+# In subgrid ONtronto, of parent CAeast
+accept -80.5041667 44.5458333 0
+expect -80.50401615833 44.5458827236 0
+-------------------------------------------------------------------------------
+
+# Subset of NTv2_0.gsb
+-------------------------------------------------------------------------------
+operation +proj=hgridshift +grids=tests/test_hgrid_with_subgrid_no_grid_name.tif
+-------------------------------------------------------------------------------
+# In subgrid ALbanff, of parent CAwest
+accept -115.5416667 51.1666667 0
+expect -115.5427092888 51.1666899972 0
+
+# In subgrid ONtronto, of parent CAeast
+accept -80.5041667 44.5458333 0
+expect -80.50401615833 44.5458827236 0
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+operation +proj=hgridshift +grids=tests/test_vgrid.tif
+-------------------------------------------------------------------------------
+expect failure errno failed_to_load_grid
+-------------------------------------------------------------------------------
+
+
+# IGNF:LAMBE to IGNF:LAMB93 using xyzgridshift operation
+-------------------------------------------------------------------------------
+operation +proj=pipeline
+ +step +inv +proj=lcc +lat_1=46.8 +lat_0=46.8 +lon_0=0
+ +k_0=0.99987742 +x_0=600000 +y_0=2200000 +ellps=clrk80ign +pm=paris
+ +step +proj=push +v_3
+ +step +proj=cart +ellps=clrk80ign
+ +step +proj=xyzgridshift +grids=tests/subset_of_gr3df97a.tif +grid_ref=output_crs +ellps=GRS80
+ +step +proj=cart +ellps=GRS80 +inv
+ +step +proj=pop +v_3
+ +step +proj=lcc +lat_0=46.5 +lon_0=3 +lat_1=49 +lat_2=44
+ +x_0=700000 +y_0=6600000 +ellps=GRS80
+-------------------------------------------------------------------------------
+tolerance 1 mm
+
+accept 814149.529 1887019.768 0
+expect 860690.804 6319036.849 0
+# If using ntf_r93.gsb, one gets: 860690.805 6319036.850
+
+roundtrip 1
+-------------------------------------------------------------------------------
+
+
+</gie>
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index 841d72b3..0dcafc85 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -147,3 +147,23 @@ target_link_libraries(gie_self_tests
add_test(NAME gie_self_tests COMMAND gie_self_tests)
set_property(TEST gie_self_tests
PROPERTY ENVIRONMENT "PROJ_LIB=${PROJECT_BINARY_DIR}/data")
+
+
+add_executable(test_network
+ main.cpp
+ test_network.cpp)
+if(CURL_FOUND)
+ include_directories(${CURL_INCLUDE_DIR})
+ target_link_libraries(test_network ${CURL_LIBRARY})
+endif()
+target_link_libraries(test_network
+ GTest::gtest
+ ${PROJ_LIBRARIES})
+add_test(NAME test_network COMMAND test_network)
+if(MSVC)
+ set_property(TEST test_network
+ PROPERTY ENVIRONMENT "PROJ_LIB=${PROJECT_BINARY_DIR}/data\\;${PROJECT_SOURCE_DIR}/data;PROJ_SOURCE_DATA=${PROJECT_SOURCE_DIR}/data")
+else()
+ set_property(TEST test_network
+ PROPERTY ENVIRONMENT "PROJ_LIB=${PROJECT_BINARY_DIR}/data:${PROJECT_SOURCE_DIR}/data;PROJ_SOURCE_DATA=${PROJECT_SOURCE_DIR}/data")
+endif()
diff --git a/test/unit/Makefile.am b/test/unit/Makefile.am
index 57a03ca8..ce11ae4e 100644
--- a/test/unit/Makefile.am
+++ b/test/unit/Makefile.am
@@ -17,6 +17,7 @@ noinst_PROGRAMS += proj_context_test
noinst_PROGRAMS += test_cpp_api
noinst_PROGRAMS += gie_self_tests
noinst_PROGRAMS += include_proj_h_from_c
+noinst_PROGRAMS += test_network
pj_transform_test_SOURCES = pj_transform_test.cpp main.cpp
pj_transform_test_LDADD = ../../src/libproj.la @GTEST_LIBS@
@@ -62,4 +63,11 @@ gie_self_tests-check: gie_self_tests
include_proj_h_from_c_SOURCES = include_proj_h_from_c.c
-check-local: pj_transform_test-check pj_phi2_test-check proj_errno_string_test-check proj_angular_io_test-check proj_context_test-check test_cpp_api-check gie_self_tests-check
+test_network_SOURCES = test_network.cpp main.cpp
+test_network_CXXFLAGS = @CURL_CFLAGS@ @CURL_ENABLED_FLAGS@
+test_network_LDADD = ../../src/libproj.la @GTEST_LIBS@ @CURL_LIBS@
+
+test_network-check: test_network
+ PROJ_LIB=$(PROJ_LIB) PROJ_SOURCE_DATA=$(PROJ_LIB) ./test_network
+
+check-local: pj_transform_test-check pj_phi2_test-check proj_errno_string_test-check proj_angular_io_test-check proj_context_test-check test_cpp_api-check gie_self_tests-check test_network-check
diff --git a/test/unit/pj_transform_test.cpp b/test/unit/pj_transform_test.cpp
index b3a061b4..5ca8dcce 100644
--- a/test/unit/pj_transform_test.cpp
+++ b/test/unit/pj_transform_test.cpp
@@ -614,4 +614,95 @@ TEST(proj_api_h, pj_set_finder) {
pj_set_finder(nullptr);
}
+// ---------------------------------------------------------------------------
+
+TEST(proj_api_h, default_fileapi) {
+ auto ctx = pj_ctx_alloc();
+ auto fid = pj_open_lib(ctx, "proj.db", "rb");
+ ASSERT_NE(fid, nullptr);
+ char header[6];
+ ASSERT_EQ(pj_ctx_fread(ctx, header, 1, 6, fid), 6U);
+ ASSERT_TRUE(memcmp(header, "SQLite", 6) == 0);
+ ASSERT_EQ(pj_ctx_ftell(ctx, fid), 6);
+ ASSERT_EQ(pj_ctx_fseek(ctx, fid, 0, SEEK_SET), 0);
+ ASSERT_EQ(pj_ctx_ftell(ctx, fid), 0);
+ pj_ctx_fclose(ctx, fid);
+ pj_ctx_free(ctx);
+}
+
+// ---------------------------------------------------------------------------
+
+struct Spy {
+ bool gotInMyFOpen = false;
+ bool gotInMyFRead = false;
+ bool gotInMyFSeek = false;
+ bool gotInMyFTell = false;
+ bool gotInMyFClose = false;
+};
+
+struct MyFile {
+ FILE *fp;
+ Spy *spy;
+};
+
+static PAFile myFOpen(projCtx ctx, const char *filename, const char *access) {
+ FILE *fp = fopen(filename, access);
+ if (!fp)
+ return nullptr;
+ MyFile *myF = new MyFile;
+ myF->spy = (Spy *)pj_ctx_get_app_data(ctx);
+ myF->spy->gotInMyFOpen = true;
+ myF->fp = fp;
+ return reinterpret_cast<PAFile>(myF);
+}
+
+static size_t myFRead(void *buffer, size_t size, size_t nmemb, PAFile file) {
+ MyFile *myF = reinterpret_cast<MyFile *>(file);
+ myF->spy->gotInMyFRead = true;
+ return fread(buffer, size, nmemb, myF->fp);
+}
+
+static int myFSeek(PAFile file, long offset, int whence) {
+ MyFile *myF = reinterpret_cast<MyFile *>(file);
+ myF->spy->gotInMyFSeek = true;
+ return fseek(myF->fp, offset, whence);
+}
+
+static long myFTell(PAFile file) {
+ MyFile *myF = reinterpret_cast<MyFile *>(file);
+ myF->spy->gotInMyFTell = true;
+ return ftell(myF->fp);
+}
+
+static void myFClose(PAFile file) {
+ MyFile *myF = reinterpret_cast<MyFile *>(file);
+ myF->spy->gotInMyFClose = true;
+ fclose(myF->fp);
+ delete myF;
+}
+
+TEST(proj_api_h, custom_fileapi) {
+ auto ctx = pj_ctx_alloc();
+ Spy spy;
+ pj_ctx_set_app_data(ctx, &spy);
+ projFileAPI myAPI = {myFOpen, myFRead, myFSeek, myFTell, myFClose};
+ pj_ctx_set_fileapi(ctx, &myAPI);
+ EXPECT_EQ(pj_ctx_get_fileapi(ctx), &myAPI);
+ auto fid = pj_open_lib(ctx, "proj.db", "rb");
+ ASSERT_NE(fid, nullptr);
+ char header[6];
+ ASSERT_EQ(pj_ctx_fread(ctx, header, 1, 6, fid), 6U);
+ ASSERT_TRUE(memcmp(header, "SQLite", 6) == 0);
+ ASSERT_EQ(pj_ctx_ftell(ctx, fid), 6);
+ ASSERT_EQ(pj_ctx_fseek(ctx, fid, 0, SEEK_SET), 0);
+ ASSERT_EQ(pj_ctx_ftell(ctx, fid), 0);
+ pj_ctx_fclose(ctx, fid);
+ pj_ctx_free(ctx);
+ EXPECT_TRUE(spy.gotInMyFOpen);
+ EXPECT_TRUE(spy.gotInMyFRead);
+ EXPECT_TRUE(spy.gotInMyFSeek);
+ EXPECT_TRUE(spy.gotInMyFTell);
+ EXPECT_TRUE(spy.gotInMyFClose);
+}
+
} // namespace
diff --git a/test/unit/test_factory.cpp b/test/unit/test_factory.cpp
index 93b2ef34..e342dad9 100644
--- a/test/unit/test_factory.cpp
+++ b/test/unit/test_factory.cpp
@@ -1617,34 +1617,34 @@ class FactoryWithTmpDatabase : public ::testing::Test {
DatabaseContext::create(m_ctxt), "OTHER");
auto res = factoryOTHER->createFromCRSCodesWithIntermediates(
"NS_SOURCE", "SOURCE", "NS_TARGET", "TARGET", false, false,
- false, {});
+ false, false, {});
EXPECT_EQ(res.size(), 1U);
EXPECT_TRUE(res.empty() ||
nn_dynamic_pointer_cast<ConcatenatedOperation>(res[0]));
res = factoryOTHER->createFromCRSCodesWithIntermediates(
"NS_SOURCE", "SOURCE", "NS_TARGET", "TARGET", false, false,
- false, {std::make_pair(std::string("NS_PIVOT"),
- std::string("PIVOT"))});
+ false, false, {std::make_pair(std::string("NS_PIVOT"),
+ std::string("PIVOT"))});
EXPECT_EQ(res.size(), 1U);
EXPECT_TRUE(res.empty() ||
nn_dynamic_pointer_cast<ConcatenatedOperation>(res[0]));
res = factoryOTHER->createFromCRSCodesWithIntermediates(
"NS_SOURCE", "SOURCE", "NS_TARGET", "TARGET", false, false,
- false, {std::make_pair(std::string("NS_PIVOT"),
- std::string("NOT_EXISTING"))});
+ false, false, {std::make_pair(std::string("NS_PIVOT"),
+ std::string("NOT_EXISTING"))});
EXPECT_EQ(res.size(), 0U);
res = factoryOTHER->createFromCRSCodesWithIntermediates(
"NS_SOURCE", "SOURCE", "NS_TARGET", "TARGET", false, false,
- false,
+ false, false,
{std::make_pair(std::string("BAD_NS"), std::string("PIVOT"))});
EXPECT_EQ(res.size(), 0U);
res = factoryOTHER->createFromCRSCodesWithIntermediates(
"NS_TARGET", "TARGET", "NS_SOURCE", "SOURCE", false, false,
- false, {});
+ false, false, {});
EXPECT_EQ(res.size(), 1U);
EXPECT_TRUE(res.empty() ||
nn_dynamic_pointer_cast<ConcatenatedOperation>(res[0]));
@@ -1654,7 +1654,7 @@ class FactoryWithTmpDatabase : public ::testing::Test {
DatabaseContext::create(m_ctxt), std::string());
auto res = factory->createFromCRSCodesWithIntermediates(
"NS_SOURCE", "SOURCE", "NS_TARGET", "TARGET", false, false,
- false, {});
+ false, false, {});
EXPECT_EQ(res.size(), 1U);
EXPECT_TRUE(res.empty() ||
nn_dynamic_pointer_cast<ConcatenatedOperation>(res[0]));
@@ -1833,7 +1833,7 @@ TEST(factory, AuthorityFactory_createFromCoordinateReferenceSystemCodes) {
{
// Test removal of superseded transform
auto list = factory->createFromCoordinateReferenceSystemCodes(
- "EPSG", "4179", "EPSG", "4258", false, false, true);
+ "EPSG", "4179", "EPSG", "4258", false, false, false, true);
ASSERT_EQ(list.size(), 2U);
// Romania has a larger area than Poland (given our approx formula)
EXPECT_EQ(list[0]->getEPSGCode(), 15994); // Romania - 3m
@@ -1851,12 +1851,12 @@ TEST(
{
auto res = factory->createFromCoordinateReferenceSystemCodes(
- "EPSG", "4326", "EPSG", "32631", false, false, false);
+ "EPSG", "4326", "EPSG", "32631", false, false, false, false);
ASSERT_EQ(res.size(), 1U);
}
{
auto res = factory->createFromCoordinateReferenceSystemCodes(
- "EPSG", "4209", "EPSG", "4326", false, false, false);
+ "EPSG", "4209", "EPSG", "4326", false, false, false, false);
EXPECT_TRUE(!res.empty());
for (const auto &conv : res) {
EXPECT_TRUE(conv->sourceCRS()->getEPSGCode() == 4209);
@@ -1889,7 +1889,8 @@ TEST_F(FactoryWithTmpDatabase,
DatabaseContext::create(m_ctxt), std::string());
{
auto res = factoryGeneral->createFromCoordinateReferenceSystemCodes(
- "OTHER", "OTHER_4326", "OTHER", "OTHER_32631", false, false, false);
+ "OTHER", "OTHER_4326", "OTHER", "OTHER_32631", false, false, false,
+ false);
ASSERT_EQ(res.size(), 1U);
}
@@ -1897,7 +1898,8 @@ TEST_F(FactoryWithTmpDatabase,
AuthorityFactory::create(DatabaseContext::create(m_ctxt), "EPSG");
{
auto res = factoryEPSG->createFromCoordinateReferenceSystemCodes(
- "OTHER", "OTHER_4326", "OTHER", "OTHER_32631", false, false, false);
+ "OTHER", "OTHER_4326", "OTHER", "OTHER_32631", false, false, false,
+ false);
ASSERT_EQ(res.size(), 1U);
}
@@ -1919,17 +1921,17 @@ TEST_F(FactoryWithTmpDatabase,
<< last_error();
{
auto res = factoryGeneral->createFromCoordinateReferenceSystemCodes(
- "EPSG", "4326", "OTHER", "OTHER_4326", false, false, false);
+ "EPSG", "4326", "OTHER", "OTHER_4326", false, false, false, false);
ASSERT_EQ(res.size(), 1U);
}
{
auto res = factoryEPSG->createFromCoordinateReferenceSystemCodes(
- "EPSG", "4326", "OTHER", "OTHER_4326", false, false, false);
+ "EPSG", "4326", "OTHER", "OTHER_4326", false, false, false, false);
ASSERT_EQ(res.size(), 0U);
}
{
auto res = factoryOTHER->createFromCoordinateReferenceSystemCodes(
- "EPSG", "4326", "OTHER", "OTHER_4326", false, false, false);
+ "EPSG", "4326", "OTHER", "OTHER_4326", false, false, false, false);
ASSERT_EQ(res.size(), 1U);
}
}
@@ -1982,7 +1984,7 @@ TEST_F(FactoryWithTmpDatabase,
auto factoryOTHER =
AuthorityFactory::create(DatabaseContext::create(m_ctxt), "OTHER");
auto res = factoryOTHER->createFromCoordinateReferenceSystemCodes(
- "EPSG", "4326", "EPSG", "4326", false, false, false);
+ "EPSG", "4326", "EPSG", "4326", false, false, false, false);
ASSERT_EQ(res.size(), 3U);
EXPECT_EQ(*(res[0]->name()->description()), "TRANSFORMATION_1M");
EXPECT_EQ(*(res[1]->name()->description()), "TRANSFORMATION_10M");
@@ -2001,7 +2003,7 @@ TEST_F(
auto factory = AuthorityFactory::create(DatabaseContext::create(m_ctxt),
std::string());
auto res = factory->createFromCRSCodesWithIntermediates(
- "EPSG", "4326", "EPSG", "4326", false, false, false, {});
+ "EPSG", "4326", "EPSG", "4326", false, false, false, false, {});
EXPECT_EQ(res.size(), 0U);
}
@@ -2085,7 +2087,7 @@ TEST_F(FactoryWithTmpDatabase, AuthorityFactory_proj_based_transformation) {
auto factoryOTHER =
AuthorityFactory::create(DatabaseContext::create(m_ctxt), "OTHER");
auto res = factoryOTHER->createFromCoordinateReferenceSystemCodes(
- "EPSG", "4326", "EPSG", "4326", false, false, false);
+ "EPSG", "4326", "EPSG", "4326", false, false, false, false);
ASSERT_EQ(res.size(), 1U);
EXPECT_EQ(res[0]->nameStr(), "My PROJ string based op");
EXPECT_EQ(res[0]->exportToPROJString(PROJStringFormatter::create().get()),
@@ -2146,7 +2148,7 @@ TEST_F(FactoryWithTmpDatabase, AuthorityFactory_wkt_based_transformation) {
auto factoryOTHER =
AuthorityFactory::create(DatabaseContext::create(m_ctxt), "OTHER");
auto res = factoryOTHER->createFromCoordinateReferenceSystemCodes(
- "EPSG", "4326", "EPSG", "4326", false, false, false);
+ "EPSG", "4326", "EPSG", "4326", false, false, false, false);
ASSERT_EQ(res.size(), 1U);
EXPECT_EQ(res[0]->nameStr(), "My WKT string based op");
EXPECT_EQ(res[0]->exportToPROJString(PROJStringFormatter::create().get()),
@@ -2180,9 +2182,10 @@ TEST_F(FactoryWithTmpDatabase,
auto factoryOTHER =
AuthorityFactory::create(DatabaseContext::create(m_ctxt), "OTHER");
- EXPECT_THROW(factoryOTHER->createFromCoordinateReferenceSystemCodes(
- "EPSG", "4326", "EPSG", "4326", false, false, false),
- FactoryException);
+ EXPECT_THROW(
+ factoryOTHER->createFromCoordinateReferenceSystemCodes(
+ "EPSG", "4326", "EPSG", "4326", false, false, false, false),
+ FactoryException);
}
// ---------------------------------------------------------------------------
@@ -2207,9 +2210,10 @@ TEST_F(FactoryWithTmpDatabase,
auto factoryOTHER =
AuthorityFactory::create(DatabaseContext::create(m_ctxt), "OTHER");
- EXPECT_THROW(factoryOTHER->createFromCoordinateReferenceSystemCodes(
- "EPSG", "4326", "EPSG", "4326", false, false, false),
- FactoryException);
+ EXPECT_THROW(
+ factoryOTHER->createFromCoordinateReferenceSystemCodes(
+ "EPSG", "4326", "EPSG", "4326", false, false, false, false),
+ FactoryException);
}
// ---------------------------------------------------------------------------
@@ -2262,7 +2266,7 @@ TEST_F(FactoryWithTmpDatabase, lookForGridInfo) {
bool openLicense = false;
bool gridAvailable = false;
EXPECT_TRUE(DatabaseContext::create(m_ctxt)->lookForGridInfo(
- "PROJ_fake_grid", fullFilename, packageName, url, directDownload,
+ "PROJ_fake_grid", false, fullFilename, packageName, url, directDownload,
openLicense, gridAvailable));
EXPECT_TRUE(fullFilename.empty());
EXPECT_TRUE(packageName.empty());
diff --git a/test/unit/test_network.cpp b/test/unit/test_network.cpp
new file mode 100644
index 00000000..5cd32f68
--- /dev/null
+++ b/test/unit/test_network.cpp
@@ -0,0 +1,879 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: Test networking
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2019, Even Rouault <even dot rouault at spatialys dot com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ****************************************************************************/
+
+#include "gtest_include.h"
+
+#include <memory>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "proj_internal.h"
+#include <proj.h>
+
+#ifdef CURL_ENABLED
+#include <curl/curl.h>
+#endif
+
+namespace {
+
+// ---------------------------------------------------------------------------
+
+#ifdef CURL_ENABLED
+
+static bool networkAccessOK = false;
+
+static size_t noop_curl_write_func(void *, size_t, size_t nmemb, void *) {
+ return nmemb;
+}
+
+TEST(networking, initial_check) {
+ CURL *hCurlHandle = curl_easy_init();
+ if (!hCurlHandle)
+ return;
+ curl_easy_setopt(hCurlHandle, CURLOPT_URL,
+ "https://cdn.proj.org/ntf_r93.tif");
+
+ curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, "0-1");
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION, noop_curl_write_func);
+
+ curl_easy_perform(hCurlHandle);
+
+ long response_code = 0;
+ curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
+
+ curl_easy_cleanup(hCurlHandle);
+
+ networkAccessOK = (response_code == 206);
+ if (!networkAccessOK) {
+ fprintf(stderr, "network access not working");
+ }
+}
+
+#endif
+
+// ---------------------------------------------------------------------------
+
+static void silent_logger(void *, int, const char *) {}
+
+// ---------------------------------------------------------------------------
+
+TEST(networking, basic) {
+ const char *pipeline =
+ "+proj=pipeline "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=hgridshift +grids=https://cdn.proj.org/ntf_r93.tif "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg";
+
+ // network access disabled by default
+ auto ctx = proj_context_create();
+ proj_log_func(ctx, nullptr, silent_logger);
+ auto P = proj_create(ctx, pipeline);
+ ASSERT_EQ(P, nullptr);
+ proj_context_destroy(ctx);
+
+#ifdef CURL_ENABLED
+ // enable through env variable
+ ctx = proj_context_create();
+ putenv(const_cast<char *>("PROJ_NETWORK=ON"));
+ P = proj_create(ctx, pipeline);
+ if (networkAccessOK) {
+ ASSERT_NE(P, nullptr);
+ }
+ proj_destroy(P);
+ proj_context_destroy(ctx);
+ putenv(const_cast<char *>("PROJ_NETWORK="));
+#endif
+
+ // still disabled
+ ctx = proj_context_create();
+ proj_log_func(ctx, nullptr, silent_logger);
+ P = proj_create(ctx, pipeline);
+ ASSERT_EQ(P, nullptr);
+ proj_context_destroy(ctx);
+
+ // enable through API
+ ctx = proj_context_create();
+ proj_context_set_enable_network(ctx, true);
+ P = proj_create(ctx, pipeline);
+#ifdef CURL_ENABLED
+ if (networkAccessOK) {
+ ASSERT_NE(P, nullptr);
+ } else {
+ ASSERT_EQ(P, nullptr);
+ proj_context_destroy(ctx);
+ return;
+ }
+ double lon = 2;
+ double lat = 49;
+ proj_trans_generic(P, PJ_FWD, &lon, sizeof(double), 1, &lat, sizeof(double),
+ 1, nullptr, 0, 0, nullptr, 0, 0);
+ EXPECT_NEAR(lon, 1.9992776848, 1e-10);
+ EXPECT_NEAR(lat, 48.9999322600, 1e-10);
+
+ proj_destroy(P);
+#else
+ ASSERT_EQ(P, nullptr);
+#endif
+ proj_context_destroy(ctx);
+}
+
+// ---------------------------------------------------------------------------
+
+#ifdef CURL_ENABLED
+
+TEST(networking, curl_invalid_resource) {
+ auto ctx = proj_context_create();
+ proj_context_set_enable_network(ctx, true);
+ proj_log_func(ctx, nullptr, silent_logger);
+ auto P = proj_create(
+ ctx, "+proj=hgridshift +grids=https://i_do_not.exist/my.tif");
+ proj_context_destroy(ctx);
+ ASSERT_EQ(P, nullptr);
+}
+#endif
+
+// ---------------------------------------------------------------------------
+
+struct Event {
+ virtual ~Event();
+ std::string type{};
+ PJ_CONTEXT *ctx = nullptr;
+};
+
+Event::~Event() = default;
+
+struct OpenEvent : public Event {
+ OpenEvent() { type = "OpenEvent"; }
+
+ std::string url{};
+ unsigned long long offset = 0;
+ size_t size_to_read = 0;
+ std::vector<unsigned char> response{};
+ std::string errorMsg{};
+ int file_id = 0;
+};
+
+struct CloseEvent : public Event {
+ CloseEvent() { type = "CloseEvent"; }
+
+ int file_id = 0;
+};
+
+struct GetHeaderValueEvent : public Event {
+ GetHeaderValueEvent() { type = "GetHeaderValueEvent"; }
+
+ int file_id = 0;
+ std::string key{};
+ std::string value{};
+};
+
+struct ReadRangeEvent : public Event {
+ ReadRangeEvent() { type = "ReadRangeEvent"; }
+
+ unsigned long long offset = 0;
+ size_t size_to_read = 0;
+ std::vector<unsigned char> response{};
+ std::string errorMsg{};
+ int file_id = 0;
+};
+
+struct File {};
+
+struct ExchangeWithCallback {
+ std::vector<std::unique_ptr<Event>> events{};
+ size_t nextEvent = 0;
+ bool error = false;
+ std::map<int, PROJ_NETWORK_HANDLE *> mapIdToHandle{};
+
+ bool allConsumedAndNoError() const {
+ return nextEvent == events.size() && !error;
+ }
+};
+
+static PROJ_NETWORK_HANDLE *open_cbk(PJ_CONTEXT *ctx, const char *url,
+ unsigned long long offset,
+ size_t size_to_read, void *buffer,
+ size_t *out_size_read,
+ size_t error_string_max_size,
+ char *out_error_string, void *user_data) {
+ auto exchange = static_cast<ExchangeWithCallback *>(user_data);
+ if (exchange->error)
+ return nullptr;
+ if (exchange->nextEvent >= exchange->events.size()) {
+ fprintf(stderr, "unexpected call to open(%s, %ld, %ld)\n", url,
+ (long)offset, (long)size_to_read);
+ exchange->error = true;
+ return nullptr;
+ }
+ auto openEvent =
+ dynamic_cast<OpenEvent *>(exchange->events[exchange->nextEvent].get());
+ if (!openEvent) {
+ fprintf(stderr, "unexpected call to open(%s, %ld, %ld). "
+ "Was expecting a %s event\n",
+ url, (long)offset, (long)size_to_read,
+ exchange->events[exchange->nextEvent]->type.c_str());
+ exchange->error = true;
+ return nullptr;
+ }
+ exchange->nextEvent++;
+ if (openEvent->ctx != ctx || openEvent->url != url ||
+ openEvent->offset != offset ||
+ openEvent->size_to_read != size_to_read) {
+ fprintf(stderr, "wrong call to open(%s, %ld, %ld). Was expecting "
+ "open(%s, %ld, %ld)\n",
+ url, (long)offset, (long)size_to_read, openEvent->url.c_str(),
+ (long)openEvent->offset, (long)openEvent->size_to_read);
+ exchange->error = true;
+ return nullptr;
+ }
+ if (!openEvent->errorMsg.empty()) {
+ snprintf(out_error_string, error_string_max_size, "%s",
+ openEvent->errorMsg.c_str());
+ return nullptr;
+ }
+
+ memcpy(buffer, openEvent->response.data(), openEvent->response.size());
+ *out_size_read = openEvent->response.size();
+ auto handle = reinterpret_cast<PROJ_NETWORK_HANDLE *>(new File());
+ exchange->mapIdToHandle[openEvent->file_id] = handle;
+ return handle;
+}
+
+static void close_cbk(PJ_CONTEXT *ctx, PROJ_NETWORK_HANDLE *handle,
+ void *user_data) {
+ auto exchange = static_cast<ExchangeWithCallback *>(user_data);
+ if (exchange->error)
+ return;
+ if (exchange->nextEvent >= exchange->events.size()) {
+ fprintf(stderr, "unexpected call to close()\n");
+ exchange->error = true;
+ return;
+ }
+ auto closeEvent =
+ dynamic_cast<CloseEvent *>(exchange->events[exchange->nextEvent].get());
+ if (!closeEvent) {
+ fprintf(stderr, "unexpected call to close(). "
+ "Was expecting a %s event\n",
+ exchange->events[exchange->nextEvent]->type.c_str());
+ exchange->error = true;
+ return;
+ }
+ if (closeEvent->ctx != ctx) {
+ fprintf(stderr, "close() called with bad context\n");
+ exchange->error = true;
+ return;
+ }
+ if (exchange->mapIdToHandle[closeEvent->file_id] != handle) {
+ fprintf(stderr, "close() called with bad handle\n");
+ exchange->error = true;
+ return;
+ }
+ exchange->nextEvent++;
+ delete reinterpret_cast<File *>(handle);
+}
+
+static const char *get_header_value_cbk(PJ_CONTEXT *ctx,
+ PROJ_NETWORK_HANDLE *handle,
+ const char *header_name,
+ void *user_data) {
+ auto exchange = static_cast<ExchangeWithCallback *>(user_data);
+ if (exchange->error)
+ return nullptr;
+ if (exchange->nextEvent >= exchange->events.size()) {
+ fprintf(stderr, "unexpected call to get_header_value()\n");
+ exchange->error = true;
+ return nullptr;
+ }
+ auto getHeaderValueEvent = dynamic_cast<GetHeaderValueEvent *>(
+ exchange->events[exchange->nextEvent].get());
+ if (!getHeaderValueEvent) {
+ fprintf(stderr, "unexpected call to get_header_value(). "
+ "Was expecting a %s event\n",
+ exchange->events[exchange->nextEvent]->type.c_str());
+ exchange->error = true;
+ return nullptr;
+ }
+ if (getHeaderValueEvent->ctx != ctx) {
+ fprintf(stderr, "get_header_value() called with bad context\n");
+ exchange->error = true;
+ return nullptr;
+ }
+ if (getHeaderValueEvent->key != header_name) {
+ fprintf(stderr, "wrong call to get_header_value(%s). Was expecting "
+ "get_header_value(%s)\n",
+ header_name, getHeaderValueEvent->key.c_str());
+ exchange->error = true;
+ return nullptr;
+ }
+ if (exchange->mapIdToHandle[getHeaderValueEvent->file_id] != handle) {
+ fprintf(stderr, "get_header_value() called with bad handle\n");
+ exchange->error = true;
+ return nullptr;
+ }
+ exchange->nextEvent++;
+ return getHeaderValueEvent->value.c_str();
+}
+
+static size_t read_range_cbk(PJ_CONTEXT *ctx, PROJ_NETWORK_HANDLE *handle,
+ unsigned long long offset, size_t size_to_read,
+ void *buffer, size_t error_string_max_size,
+ char *out_error_string, void *user_data) {
+ auto exchange = static_cast<ExchangeWithCallback *>(user_data);
+ if (exchange->error)
+ return 0;
+ if (exchange->nextEvent >= exchange->events.size()) {
+ fprintf(stderr, "unexpected call to read_range(%ld, %ld)\n",
+ (long)offset, (long)size_to_read);
+ exchange->error = true;
+ return 0;
+ }
+ auto readRangeEvent = dynamic_cast<ReadRangeEvent *>(
+ exchange->events[exchange->nextEvent].get());
+ if (!readRangeEvent) {
+ fprintf(stderr, "unexpected call to read_range(). "
+ "Was expecting a %s event\n",
+ exchange->events[exchange->nextEvent]->type.c_str());
+ exchange->error = true;
+ return 0;
+ }
+ if (exchange->mapIdToHandle[readRangeEvent->file_id] != handle) {
+ fprintf(stderr, "read_range() called with bad handle\n");
+ exchange->error = true;
+ return 0;
+ }
+ if (readRangeEvent->ctx != ctx || readRangeEvent->offset != offset ||
+ readRangeEvent->size_to_read != size_to_read) {
+ fprintf(stderr, "wrong call to read_range(%ld, %ld). Was expecting "
+ "read_range(%ld, %ld)\n",
+ (long)offset, (long)size_to_read, (long)readRangeEvent->offset,
+ (long)readRangeEvent->size_to_read);
+ exchange->error = true;
+ return 0;
+ }
+ exchange->nextEvent++;
+ if (!readRangeEvent->errorMsg.empty()) {
+ snprintf(out_error_string, error_string_max_size, "%s",
+ readRangeEvent->errorMsg.c_str());
+ return 0;
+ }
+ memcpy(buffer, readRangeEvent->response.data(),
+ readRangeEvent->response.size());
+ return readRangeEvent->response.size();
+}
+
+TEST(networking, custom) {
+ auto ctx = proj_context_create();
+ proj_context_set_enable_network(ctx, true);
+ ExchangeWithCallback exchange;
+ ASSERT_TRUE(proj_context_set_network_callbacks(ctx, open_cbk, close_cbk,
+ get_header_value_cbk,
+ read_range_cbk, &exchange));
+
+ {
+ std::unique_ptr<OpenEvent> event(new OpenEvent());
+ event->ctx = ctx;
+ event->url = "https://foo/my.tif";
+ event->offset = 0;
+ event->size_to_read = 16384;
+ event->response.resize(16384);
+ event->file_id = 1;
+
+ const char *proj_source_data = getenv("PROJ_SOURCE_DATA");
+ ASSERT_TRUE(proj_source_data != nullptr);
+ std::string filename(proj_source_data);
+ filename += "/tests/egm96_15_uncompressed_truncated.tif";
+ FILE *f = fopen(filename.c_str(), "rb");
+ ASSERT_TRUE(f != nullptr);
+ ASSERT_EQ(fread(&event->response[0], 1, 956, f), 956U);
+ fclose(f);
+ exchange.events.emplace_back(std::move(event));
+ }
+ {
+ std::unique_ptr<GetHeaderValueEvent> event(new GetHeaderValueEvent());
+ event->ctx = ctx;
+ event->key = "Content-Range";
+ event->value = "dummy"; // dummy value: not used
+ event->file_id = 1;
+ exchange.events.emplace_back(std::move(event));
+ }
+ {
+ std::unique_ptr<CloseEvent> event(new CloseEvent());
+ event->ctx = ctx;
+ event->file_id = 1;
+ exchange.events.emplace_back(std::move(event));
+ }
+
+ auto P = proj_create(
+ ctx, "+proj=vgridshift +grids=https://foo/my.tif +multiplier=1");
+
+ ASSERT_NE(P, nullptr);
+ ASSERT_TRUE(exchange.allConsumedAndNoError());
+
+ {
+ std::unique_ptr<OpenEvent> event(new OpenEvent());
+ event->ctx = ctx;
+ event->url = "https://foo/my.tif";
+ event->offset = 524288;
+ event->size_to_read = 278528;
+ event->response.resize(278528);
+ event->file_id = 2;
+ float f = 1.25;
+ for (size_t i = 0; i < 278528 / sizeof(float); i++) {
+ memcpy(&event->response[i * sizeof(float)], &f, sizeof(float));
+ }
+ exchange.events.emplace_back(std::move(event));
+ }
+
+ {
+ double lon = 2 / 180. * M_PI;
+ double lat = 49 / 180. * M_PI;
+ double z = 0;
+ ASSERT_EQ(proj_trans_generic(P, PJ_FWD, &lon, sizeof(double), 1, &lat,
+ sizeof(double), 1, &z, sizeof(double), 1,
+ nullptr, 0, 0),
+ 1U);
+ EXPECT_EQ(z, 1.25);
+ }
+
+ ASSERT_TRUE(exchange.allConsumedAndNoError());
+
+ {
+ std::unique_ptr<ReadRangeEvent> event(new ReadRangeEvent());
+ event->ctx = ctx;
+ event->offset = 3670016;
+ event->size_to_read = 278528;
+ event->response.resize(278528);
+ event->file_id = 2;
+ float f = 2.25;
+ for (size_t i = 0; i < 278528 / sizeof(float); i++) {
+ memcpy(&event->response[i * sizeof(float)], &f, sizeof(float));
+ }
+ exchange.events.emplace_back(std::move(event));
+ }
+
+ {
+ double lon = 2 / 180. * M_PI;
+ double lat = -49 / 180. * M_PI;
+ double z = 0;
+ ASSERT_EQ(proj_trans_generic(P, PJ_FWD, &lon, sizeof(double), 1, &lat,
+ sizeof(double), 1, &z, sizeof(double), 1,
+ nullptr, 0, 0),
+ 1U);
+ EXPECT_EQ(z, 2.25);
+ }
+ {
+ std::unique_ptr<CloseEvent> event(new CloseEvent());
+ event->ctx = ctx;
+ event->file_id = 2;
+ exchange.events.emplace_back(std::move(event));
+ }
+ proj_destroy(P);
+
+ ASSERT_TRUE(exchange.allConsumedAndNoError());
+
+ // Once again ! No network access
+
+ P = proj_create(ctx,
+ "+proj=vgridshift +grids=https://foo/my.tif +multiplier=1");
+ ASSERT_NE(P, nullptr);
+
+ {
+ double lon = 2 / 180. * M_PI;
+ double lat = 49 / 180. * M_PI;
+ double z = 0;
+ ASSERT_EQ(proj_trans_generic(P, PJ_FWD, &lon, sizeof(double), 1, &lat,
+ sizeof(double), 1, &z, sizeof(double), 1,
+ nullptr, 0, 0),
+ 1U);
+ EXPECT_EQ(z, 1.25);
+ }
+
+ proj_destroy(P);
+
+ ASSERT_TRUE(exchange.allConsumedAndNoError());
+
+ proj_context_destroy(ctx);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(networking, getfilesize) {
+ auto ctx = proj_context_create();
+ proj_context_set_enable_network(ctx, true);
+ ExchangeWithCallback exchange;
+ ASSERT_TRUE(proj_context_set_network_callbacks(ctx, open_cbk, close_cbk,
+ get_header_value_cbk,
+ read_range_cbk, &exchange));
+
+ {
+ std::unique_ptr<OpenEvent> event(new OpenEvent());
+ event->ctx = ctx;
+ event->url = "https://foo/getfilesize.tif";
+ event->offset = 0;
+ event->size_to_read = 16384;
+ event->response.resize(16384);
+ event->file_id = 1;
+
+ const char *proj_source_data = getenv("PROJ_SOURCE_DATA");
+ ASSERT_TRUE(proj_source_data != nullptr);
+ std::string filename(proj_source_data);
+ filename += "/tests/test_vgrid_single_strip_truncated.tif";
+ FILE *f = fopen(filename.c_str(), "rb");
+ ASSERT_TRUE(f != nullptr);
+ ASSERT_EQ(fread(&event->response[0], 1, 550, f), 550U);
+ fclose(f);
+ exchange.events.emplace_back(std::move(event));
+ }
+ {
+ std::unique_ptr<GetHeaderValueEvent> event(new GetHeaderValueEvent());
+ event->ctx = ctx;
+ event->key = "Content-Range";
+ event->value = "bytes 0-16383/4153510";
+ event->file_id = 1;
+ exchange.events.emplace_back(std::move(event));
+ }
+ {
+ std::unique_ptr<CloseEvent> event(new CloseEvent());
+ event->ctx = ctx;
+ event->file_id = 1;
+ exchange.events.emplace_back(std::move(event));
+ }
+
+ auto P = proj_create(
+ ctx,
+ "+proj=vgridshift +grids=https://foo/getfilesize.tif +multiplier=1");
+
+ ASSERT_NE(P, nullptr);
+ ASSERT_TRUE(exchange.allConsumedAndNoError());
+
+ proj_destroy(P);
+
+ P = proj_create(
+ ctx,
+ "+proj=vgridshift +grids=https://foo/getfilesize.tif +multiplier=1");
+
+ ASSERT_NE(P, nullptr);
+ ASSERT_TRUE(exchange.allConsumedAndNoError());
+
+ proj_destroy(P);
+
+ proj_context_destroy(ctx);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(networking, simul_open_error) {
+ auto ctx = proj_context_create();
+ proj_log_func(ctx, nullptr, silent_logger);
+ proj_context_set_enable_network(ctx, true);
+ ExchangeWithCallback exchange;
+ ASSERT_TRUE(proj_context_set_network_callbacks(ctx, open_cbk, close_cbk,
+ get_header_value_cbk,
+ read_range_cbk, &exchange));
+
+ {
+ std::unique_ptr<OpenEvent> event(new OpenEvent());
+ event->ctx = ctx;
+ event->url = "https://foo/open_error.tif";
+ event->offset = 0;
+ event->size_to_read = 16384;
+ event->errorMsg = "Cannot open file";
+ event->file_id = 1;
+
+ exchange.events.emplace_back(std::move(event));
+ }
+
+ auto P = proj_create(
+ ctx,
+ "+proj=vgridshift +grids=https://foo/open_error.tif +multiplier=1");
+
+ ASSERT_EQ(P, nullptr);
+ ASSERT_TRUE(exchange.allConsumedAndNoError());
+
+ proj_context_destroy(ctx);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(networking, simul_read_range_error) {
+ auto ctx = proj_context_create();
+ proj_context_set_enable_network(ctx, true);
+ ExchangeWithCallback exchange;
+ ASSERT_TRUE(proj_context_set_network_callbacks(ctx, open_cbk, close_cbk,
+ get_header_value_cbk,
+ read_range_cbk, &exchange));
+
+ {
+ std::unique_ptr<OpenEvent> event(new OpenEvent());
+ event->ctx = ctx;
+ event->url = "https://foo/read_range_error.tif";
+ event->offset = 0;
+ event->size_to_read = 16384;
+ event->response.resize(16384);
+ event->file_id = 1;
+
+ const char *proj_source_data = getenv("PROJ_SOURCE_DATA");
+ ASSERT_TRUE(proj_source_data != nullptr);
+ std::string filename(proj_source_data);
+ filename += "/tests/egm96_15_uncompressed_truncated.tif";
+ FILE *f = fopen(filename.c_str(), "rb");
+ ASSERT_TRUE(f != nullptr);
+ ASSERT_EQ(fread(&event->response[0], 1, 956, f), 956U);
+ fclose(f);
+ exchange.events.emplace_back(std::move(event));
+ }
+ {
+ std::unique_ptr<GetHeaderValueEvent> event(new GetHeaderValueEvent());
+ event->ctx = ctx;
+ event->key = "Content-Range";
+ event->value = "dummy"; // dummy value: not used
+ event->file_id = 1;
+ exchange.events.emplace_back(std::move(event));
+ }
+ {
+ std::unique_ptr<CloseEvent> event(new CloseEvent());
+ event->ctx = ctx;
+ event->file_id = 1;
+ exchange.events.emplace_back(std::move(event));
+ }
+
+ auto P = proj_create(ctx, "+proj=vgridshift "
+ "+grids=https://foo/read_range_error.tif "
+ "+multiplier=1");
+
+ ASSERT_NE(P, nullptr);
+ ASSERT_TRUE(exchange.allConsumedAndNoError());
+
+ {
+ std::unique_ptr<OpenEvent> event(new OpenEvent());
+ event->ctx = ctx;
+ event->url = "https://foo/read_range_error.tif";
+ event->offset = 524288;
+ event->size_to_read = 278528;
+ event->response.resize(278528);
+ event->file_id = 2;
+ float f = 1.25;
+ for (size_t i = 0; i < 278528 / sizeof(float); i++) {
+ memcpy(&event->response[i * sizeof(float)], &f, sizeof(float));
+ }
+ exchange.events.emplace_back(std::move(event));
+ }
+
+ {
+ double lon = 2 / 180. * M_PI;
+ double lat = 49 / 180. * M_PI;
+ double z = 0;
+ ASSERT_EQ(proj_trans_generic(P, PJ_FWD, &lon, sizeof(double), 1, &lat,
+ sizeof(double), 1, &z, sizeof(double), 1,
+ nullptr, 0, 0),
+ 1U);
+ EXPECT_EQ(z, 1.25);
+ }
+
+ ASSERT_TRUE(exchange.allConsumedAndNoError());
+
+ {
+ std::unique_ptr<ReadRangeEvent> event(new ReadRangeEvent());
+ event->ctx = ctx;
+ event->offset = 3670016;
+ event->size_to_read = 278528;
+ event->errorMsg = "read range error";
+ event->file_id = 2;
+ exchange.events.emplace_back(std::move(event));
+ }
+
+ {
+ double lon = 2 / 180. * M_PI;
+ double lat = -49 / 180. * M_PI;
+ double z = 0;
+ proj_log_func(ctx, nullptr, silent_logger);
+ ASSERT_EQ(proj_trans_generic(P, PJ_FWD, &lon, sizeof(double), 1, &lat,
+ sizeof(double), 1, &z, sizeof(double), 1,
+ nullptr, 0, 0),
+ 1U);
+ EXPECT_EQ(z, HUGE_VAL);
+ }
+ {
+ std::unique_ptr<CloseEvent> event(new CloseEvent());
+ event->ctx = ctx;
+ event->file_id = 2;
+ exchange.events.emplace_back(std::move(event));
+ }
+ proj_destroy(P);
+
+ ASSERT_TRUE(exchange.allConsumedAndNoError());
+
+ proj_context_destroy(ctx);
+}
+
+// ---------------------------------------------------------------------------
+
+#ifdef CURL_ENABLED
+
+TEST(networking, curl_hgridshift) {
+ auto ctx = proj_context_create();
+ proj_context_set_enable_network(ctx, true);
+
+ // NAD83 to NAD83(HARN) in West-Virginia. Using wvhpgn.tif
+ auto P = proj_create_crs_to_crs(ctx, "EPSG:4269", "EPSG:4152", nullptr);
+ ASSERT_NE(P, nullptr);
+
+ PJ_COORD c;
+ c.xyz.x = 40; // lat
+ c.xyz.y = -80; // lon
+ c.xyz.z = 0;
+ c = proj_trans(P, PJ_FWD, c);
+
+ proj_assign_context(P, ctx); // (dummy) test context reassignment
+
+ proj_destroy(P);
+ proj_context_destroy(ctx);
+
+ EXPECT_NEAR(c.xyz.x, 39.99999839, 1e-8);
+ EXPECT_NEAR(c.xyz.y, -79.99999807, 1e-8);
+ EXPECT_NEAR(c.xyz.z, 0, 1e-2);
+}
+
+#endif
+
+// ---------------------------------------------------------------------------
+
+#ifdef CURL_ENABLED
+
+TEST(networking, curl_vgridshift) {
+ auto ctx = proj_context_create();
+ proj_context_set_enable_network(ctx, true);
+
+ // WGS84 to EGM2008 height. Using egm08_25.tif
+ auto P =
+ proj_create_crs_to_crs(ctx, "EPSG:4326", "EPSG:4326+3855", nullptr);
+ ASSERT_NE(P, nullptr);
+
+ PJ_COORD c;
+ c.xyz.x = -30; // lat
+ c.xyz.y = 150; // lon
+ c.xyz.z = 0;
+ c = proj_trans(P, PJ_FWD, c);
+
+ proj_assign_context(P, ctx); // (dummy) test context reassignment
+
+ proj_destroy(P);
+ proj_context_destroy(ctx);
+
+ EXPECT_NEAR(c.xyz.x, -30, 1e-8);
+ EXPECT_NEAR(c.xyz.y, 150, 1e-8);
+ EXPECT_NEAR(c.xyz.z, -31.89, 1e-2);
+}
+
+#endif
+
+// ---------------------------------------------------------------------------
+
+#ifdef CURL_ENABLED
+
+TEST(networking, curl_vgridshift_vertcon) {
+ auto ctx = proj_context_create();
+ proj_context_set_enable_network(ctx, true);
+
+ // NGVD29 to NAVD88 height. Using vertcone.tif
+ auto P = proj_create_crs_to_crs(ctx, "EPSG:4269+7968", "EPSG:4269+5703",
+ nullptr);
+ ASSERT_NE(P, nullptr);
+
+ PJ_COORD c;
+ c.xyz.x = 40; // lat
+ c.xyz.y = -80; // lon
+ c.xyz.z = 0;
+ c = proj_trans(P, PJ_FWD, c);
+
+ proj_destroy(P);
+ proj_context_destroy(ctx);
+
+ EXPECT_NEAR(c.xyz.x, 40, 1e-8);
+ EXPECT_NEAR(c.xyz.y, -80, 1e-8);
+ EXPECT_NEAR(c.xyz.z, -0.15, 1e-2);
+}
+
+#endif
+
+// ---------------------------------------------------------------------------
+
+#ifdef CURL_ENABLED
+
+TEST(networking, network_endpoint_env_variable) {
+ putenv(const_cast<char *>("PROJ_NETWORK_ENDPOINT=http://0.0.0.0/"));
+ auto ctx = proj_context_create();
+ proj_context_set_enable_network(ctx, true);
+
+ // NAD83 to NAD83(HARN) in West-Virginia. Using wvhpgn.tif
+ auto P = proj_create_crs_to_crs(ctx, "EPSG:4269", "EPSG:4152", nullptr);
+ ASSERT_NE(P, nullptr);
+
+ PJ_COORD c;
+ c.xyz.x = 40; // lat
+ c.xyz.y = -80; // lon
+ c.xyz.z = 0;
+ c = proj_trans(P, PJ_FWD, c);
+ putenv(const_cast<char *>("PROJ_NETWORK_ENDPOINT="));
+
+ proj_destroy(P);
+ proj_context_destroy(ctx);
+
+ EXPECT_EQ(c.xyz.x, HUGE_VAL);
+}
+
+#endif
+
+// ---------------------------------------------------------------------------
+
+#ifdef CURL_ENABLED
+
+TEST(networking, network_endpoint_api) {
+ auto ctx = proj_context_create();
+ proj_context_set_enable_network(ctx, true);
+ proj_context_set_url_endpoint(ctx, "http://0.0.0.0");
+
+ // NAD83 to NAD83(HARN) in West-Virginia. Using wvhpgn.tif
+ auto P = proj_create_crs_to_crs(ctx, "EPSG:4269", "EPSG:4152", nullptr);
+ ASSERT_NE(P, nullptr);
+
+ PJ_COORD c;
+ c.xyz.x = 40; // lat
+ c.xyz.y = -80; // lon
+ c.xyz.z = 0;
+ c = proj_trans(P, PJ_FWD, c);
+
+ proj_destroy(P);
+ proj_context_destroy(ctx);
+
+ EXPECT_EQ(c.xyz.x, HUGE_VAL);
+}
+
+#endif
+
+} // namespace
diff --git a/test/unit/test_operation.cpp b/test/unit/test_operation.cpp
index 9cde8822..753878c5 100644
--- a/test/unit/test_operation.cpp
+++ b/test/unit/test_operation.cpp
@@ -4789,7 +4789,7 @@ TEST(operation, geogCRS_to_geogCRS_context_concatenated_operation) {
EXPECT_TRUE(nn_dynamic_pointer_cast<ConcatenatedOperation>(list[0]) !=
nullptr);
- auto grids = list[0]->gridsNeeded(DatabaseContext::create());
+ auto grids = list[0]->gridsNeeded(DatabaseContext::create(), false);
EXPECT_EQ(grids.size(), 1U);
}
@@ -6402,7 +6402,7 @@ TEST(operation, transformation_height_to_PROJ_string) {
EXPECT_EQ(transf->exportToPROJString(PROJStringFormatter::create().get()),
"+proj=vgridshift +grids=egm08_25.gtx +multiplier=1");
- auto grids = transf->gridsNeeded(DatabaseContext::create());
+ auto grids = transf->gridsNeeded(DatabaseContext::create(), false);
ASSERT_EQ(grids.size(), 1U);
auto gridDesc = *(grids.begin());
EXPECT_EQ(gridDesc.shortName, "egm08_25.gtx");
@@ -6702,7 +6702,7 @@ TEST(operation, compoundCRS_with_boundGeogCRS_and_boundVerticalCRS_to_geogCRS) {
"+step +proj=unitconvert +xy_in=rad +xy_out=deg "
"+step +proj=axisswap +order=2,1");
- auto grids = op->gridsNeeded(DatabaseContext::create());
+ auto grids = op->gridsNeeded(DatabaseContext::create(), false);
EXPECT_EQ(grids.size(), 1U);
auto opInverse = CoordinateOperationFactory::create()->createOperation(
@@ -8128,8 +8128,8 @@ TEST(operation, isPROJInstantiable) {
auto transformation = Transformation::createGeocentricTranslations(
PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326,
1.0, 2.0, 3.0, {});
- EXPECT_TRUE(
- transformation->isPROJInstantiable(DatabaseContext::create()));
+ EXPECT_TRUE(transformation->isPROJInstantiable(
+ DatabaseContext::create(), false));
}
// Missing grid
@@ -8137,8 +8137,8 @@ TEST(operation, isPROJInstantiable) {
auto transformation = Transformation::createNTv2(
PropertyMap(), GeographicCRS::EPSG_4807, GeographicCRS::EPSG_4326,
"foo.gsb", std::vector<PositionalAccuracyNNPtr>());
- EXPECT_FALSE(
- transformation->isPROJInstantiable(DatabaseContext::create()));
+ EXPECT_FALSE(transformation->isPROJInstantiable(
+ DatabaseContext::create(), false));
}
// Unsupported method
@@ -8149,8 +8149,8 @@ TEST(operation, isPROJInstantiable) {
PropertyMap(), std::vector<OperationParameterNNPtr>{}),
std::vector<GeneralParameterValueNNPtr>{},
std::vector<PositionalAccuracyNNPtr>{});
- EXPECT_FALSE(
- transformation->isPROJInstantiable(DatabaseContext::create()));
+ EXPECT_FALSE(transformation->isPROJInstantiable(
+ DatabaseContext::create(), false));
}
}
diff --git a/travis/csa/before_install.sh b/travis/csa/before_install.sh
index c2e29064..dcb7733b 100755
--- a/travis/csa/before_install.sh
+++ b/travis/csa/before_install.sh
@@ -5,7 +5,7 @@ set -e
./travis/before_install_apt.sh
./travis/before_install_pip.sh
-sudo apt-get install -qq sqlite3 libsqlite3-dev
+sudo apt-get install -qq sqlite3 libsqlite3-dev libtiff-dev libcurl4-openssl-dev
CLANG_LLVM=clang+llvm-6.0.0-x86_64-linux-gnu-ubuntu-16.04
wget http://releases.llvm.org/6.0.0/$CLANG_LLVM.tar.xz
diff --git a/travis/linux_clang/before_install.sh b/travis/linux_clang/before_install.sh
index 8ce465a2..c4b8acad 100755
--- a/travis/linux_clang/before_install.sh
+++ b/travis/linux_clang/before_install.sh
@@ -5,4 +5,4 @@ set -e
./travis/before_install_apt.sh
./travis/before_install_pip.sh
-sudo apt-get install -qq sqlite3 libsqlite3-dev
+sudo apt-get install -qq sqlite3 libsqlite3-dev libtiff-dev libcurl4-openssl-dev
diff --git a/travis/linux_gcc/before_install.sh b/travis/linux_gcc/before_install.sh
index 7725b3ef..9db2ea89 100755
--- a/travis/linux_gcc/before_install.sh
+++ b/travis/linux_gcc/before_install.sh
@@ -9,6 +9,7 @@ sudo apt-get install -qq \
lcov \
doxygen graphviz \
sqlite3 libsqlite3-dev \
+ libtiff-dev libcurl4-openssl-dev \
cppcheck
scripts/cppcheck.sh
diff --git a/travis/linux_gcc7/before_install.sh b/travis/linux_gcc7/before_install.sh
index a3f6c8c0..2ef2feee 100755
--- a/travis/linux_gcc7/before_install.sh
+++ b/travis/linux_gcc7/before_install.sh
@@ -8,7 +8,8 @@ set -e
sudo apt-get install -qq \
lcov \
doxygen graphviz \
- sqlite3 libsqlite3-dev
+ sqlite3 libsqlite3-dev \
+ libtiff-dev libcurl4-openssl-dev
#scripts/cppcheck.sh
#scripts/doxygen.sh
diff --git a/travis/mingw32/install.sh b/travis/mingw32/install.sh
index 52e4f7a6..abe79c7b 100755
--- a/travis/mingw32/install.sh
+++ b/travis/mingw32/install.sh
@@ -2,6 +2,9 @@
set -e
+unset CC
+unset CXX
+
export CCACHE_CPP2=yes
export PROJ_DB_CACHE_DIR="$HOME/.ccache"
@@ -16,6 +19,22 @@ ln -s $MINGW_PREFIX/libgcc_s_seh-1.dll $WINE_SYSDIR
ln -s $MINGW_PREFIX/libgcc_s_sjlj-1.dll $WINE_SYSDIR
ln -s /usr/$MINGW_ARCH/lib/libwinpthread-1.dll $WINE_SYSDIR
+# build zlib
+wget https://github.com/madler/zlib/archive/v1.2.11.tar.gz
+tar xzf v1.2.11.tar.gz
+(cd zlib-1.2.11 && sudo make install -fwin32/Makefile.gcc SHARED_MODE=1 PREFIX=x86_64-w64-mingw32- DESTDIR=/usr/$MINGW_ARCH/)
+sudo mkdir -p /usr/$MINGW_ARCH/include
+sudo mkdir -p /usr/$MINGW_ARCH/lib
+sudo cp /usr/$MINGW_ARCH/*.h /usr/$MINGW_ARCH/include
+sudo cp /usr/$MINGW_ARCH/libz.* /usr/$MINGW_ARCH/lib
+ln -s /usr/$MINGW_ARCH/zlib1.dll $WINE_SYSDIR
+
+# build libtiff
+wget https://download.osgeo.org/libtiff/tiff-4.1.0.tar.gz
+tar xzf tiff-4.1.0.tar.gz
+(cd tiff-4.1.0 && ./configure --host=$MINGW_ARCH --prefix=/usr/$MINGW_ARCH && make -j2 && sudo make install)
+ln -s /usr/$MINGW_ARCH/bin/libtiff-5.dll $WINE_SYSDIR
+
# build sqlite3
wget https://sqlite.org/2018/sqlite-autoconf-3250100.tar.gz
tar xzf sqlite-autoconf-3250100.tar.gz
@@ -28,7 +47,7 @@ ln -s /usr/$MINGW_ARCH/bin/libsqlite3-0.dll $WINE_SYSDIR
# autoconf build
mkdir build_autoconf
cd build_autoconf
-CC="ccache $MINGW_ARCH-gcc" CXX="ccache $MINGW_ARCH-g++" LD=$MINGW_ARCH-ld ../configure --host=$MINGW_ARCH --prefix=/tmp/proj_autoconf_install
+CC="ccache $MINGW_ARCH-gcc" CXX="ccache $MINGW_ARCH-g++" LD=$MINGW_ARCH-ld ../configure --host=$MINGW_ARCH --prefix=/tmp/proj_autoconf_install --without-curl
make -j2
make install
make dist-all
@@ -44,7 +63,7 @@ cd ..
# autoconf build with grids
mkdir build_autoconf_grids
cd build_autoconf_grids
-CC="ccache $MINGW_ARCH-gcc" CXX="ccache $MINGW_ARCH-g++" LD=$MINGW_ARCH-ld ../configure --host=$MINGW_ARCH --prefix=/tmp/proj_autoconf_install_nad
+CC="ccache $MINGW_ARCH-gcc" CXX="ccache $MINGW_ARCH-g++" LD=$MINGW_ARCH-ld ../configure --host=$MINGW_ARCH --prefix=/tmp/proj_autoconf_install_nad --without-curl
make -j2
make install
find /tmp/proj_autoconf_install_nad
diff --git a/travis/osx/before_install.sh b/travis/osx/before_install.sh
index 09a7a74f..e457ec8c 100755
--- a/travis/osx/before_install.sh
+++ b/travis/osx/before_install.sh
@@ -6,10 +6,11 @@ export PATH=$HOME/Library/Python/3.7/bin:$PATH
brew update
brew install ccache
-brew install sqlite3
+#brew upgrade sqlite3
+#brew upgrade libtiff
brew install doxygen
-brew install md5sha1sum
-brew reinstall python
+#brew install md5sha1sum
+#brew reinstall python
brew reinstall wget
./travis/before_install_pip.sh