diff options
| author | Even Rouault <even.rouault@spatialys.com> | 2020-01-22 18:31:26 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-01-22 18:31:26 +0100 |
| commit | db31b6dfa9c8fe37d5706d95ce81012b8db3c3b9 (patch) | |
| tree | dc592c2b56f8af476c42a51f5dbc6ee04fabc280 /test | |
| parent | 1ad703a58ce1867fe2ede96ebced1bdec9c63d65 (diff) | |
| download | PROJ-db31b6dfa9c8fe37d5706d95ce81012b8db3c3b9.tar.gz PROJ-db31b6dfa9c8fe37d5706d95ce81012b8db3c3b9.zip | |
Merge RFC4 (#1865)
This commit is the result of the squashing of rfc4_dev branch in a single
commit. It implements mostly RFC 4 related work.
* Grid handling:
- remove obsolete and presumably unfinished implementation of grid catalog functionality
- all grid functionality is in grids.cpp/.hpp
- vertical and horizontal grid shift: rework to no longer load whole grid into memory
- remove hgrids and vgrids member from PJ structure, and store them in hgridshift/vgridshift/deformation structures
- build systems: add optional libtiff dependency. Must be explicitly disabled if not desired
- add support for horizontal and vertical grids in GeoTIFF, if libtiff is available
- add GenericShiftGridSet and GenericShiftGrid classes, relying on TIFF grids, that can be used for generic purpose grid-based adjustment
- add a +proj=xyzgridshift method to perform geocentric translation by grid. Used for French NTF to RGF93 transformation using gr3df97a.tif grid
- deformation: add support for +grids= for GeoTIFF grids
- horizontal grid shift: fix failures on points slightly outside a subgrid (fixes #209)
* File management:
- add a filemanager.cpp/.hpp to deal with file related work
- test for legacy proj_api.h fileapi
- proj.h: add proj_context_set_fileapi() and proj_context_set_sqlite3_vfs_name() (fixes #866)
- add capability to read resource files from the user writable directory
* Network access:
- build systems: add optional curl dependency
- add a curl-based default implementation for network related functionality
- proj.h: add C API to control network functionality, and optionaly provide network callbacks
- add data/proj.ini with default settings
- add a SQLite3 local cache of downloaded chunks
- add proj_is_download_needed() and proj_download_file()
* Use Win32 Unicode APIs and expect all strings to be UTF-8 (fixes #1765)
For backward compatibility, if PROJ_LIB content is found to be not UTF-8 or
pointing to a non existing directory, then an attempt at interpretating it
in the ANSI page encoding is done.
proj_context_set_search_paths() now assumes strings to be in UTF-8, and
functions returning paths will also return values in UTF-8.
Diffstat (limited to 'test')
| -rw-r--r-- | test/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | test/cli/Makefile.am | 12 | ||||
| -rw-r--r-- | test/cli/ntv2_out.dist | 8 | ||||
| -rw-r--r-- | test/cli/td_out.dist | 2 | ||||
| -rwxr-xr-x | test/cli/test27 | 2 | ||||
| -rwxr-xr-x | test/cli/test83 | 2 | ||||
| -rwxr-xr-x | test/cli/testdatumfile | 6 | ||||
| -rwxr-xr-x | test/cli/testntv2 | 13 | ||||
| -rw-r--r-- | test/gie/4D-API_cs2cs-style.gie | 14 | ||||
| -rw-r--r-- | test/gie/Makefile.am | 26 | ||||
| -rw-r--r-- | test/gie/deformation.gie | 29 | ||||
| -rw-r--r-- | test/gie/geotiff_grids.gie | 324 | ||||
| -rw-r--r-- | test/gigs/Makefile.am | 34 | ||||
| -rw-r--r-- | test/unit/CMakeLists.txt | 33 | ||||
| -rw-r--r-- | test/unit/Makefile.am | 20 | ||||
| -rw-r--r-- | test/unit/gie_self_tests.cpp | 5 | ||||
| -rw-r--r-- | test/unit/pj_transform_test.cpp | 89 | ||||
| -rw-r--r-- | test/unit/proj_context_test.cpp | 63 | ||||
| -rw-r--r-- | test/unit/test_c_api.cpp | 21 | ||||
| -rw-r--r-- | test/unit/test_factory.cpp | 58 | ||||
| -rw-r--r-- | test/unit/test_grids.cpp | 227 | ||||
| -rw-r--r-- | test/unit/test_network.cpp | 1856 | ||||
| -rw-r--r-- | test/unit/test_operation.cpp | 18 |
23 files changed, 2759 insertions, 104 deletions
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/Makefile.am b/test/cli/Makefile.am index 33878743..417ec137 100644 --- a/test/cli/Makefile.am +++ b/test/cli/Makefile.am @@ -29,7 +29,7 @@ EXTRA_DIST = pj_out27.dist pj_out83.dist td_out.dist \ CMakeLists.txt testprojinfo-check: - PROJ_LIB=$(PROJ_LIB) $(TESTPROJINFO) $(PROJINFOEXE) + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(TESTPROJINFO) $(PROJINFOEXE) test27-check: $(TEST27) $(PROJEXE) @@ -41,24 +41,24 @@ testproj-check: $(TESTPROJ) $(PROJEXE) testvarious-check: - PROJ_LIB=$(PROJ_LIB) $(TESTVARIOUS) $(CS2CSEXE) + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(TESTVARIOUS) $(CS2CSEXE) testdatumfile-check: @if [ -f $(PROJ_LIB)/conus -a -f $(PROJ_LIB)/ntv1_can.dat -a -f $(PROJ_LIB)/MD -a -f $(PROJ_LIB)/ntf_r93.gsb -a -f $(PROJ_LIB)/egm96_15.gtx ]; then \ - PROJ_LIB=$(PROJ_LIB) $(TESTDATUMFILE) $(CS2CSEXE) ; \ + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(TESTDATUMFILE) $(CS2CSEXE) ; \ fi testign-check: @if [ -f $(PROJ_LIB)/ntf_r93.gsb ] ; then \ - PROJ_LIB=$(PROJ_LIB) $(TESTIGN) $(CS2CSEXE) ; \ + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(TESTIGN) $(CS2CSEXE) ; \ fi testntv2-check: @if [ -f $(PROJ_LIB)/ntv2_0.gsb -a -f $(PROJ_LIB)/conus -a -f $(PROJ_LIB)/ntv1_can.dat ] ; then \ - PROJ_LIB=$(PROJ_LIB) $(TESTNTV2) $(CS2CSEXE) ; \ + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(TESTNTV2) $(CS2CSEXE) ; \ fi testcct-check: - PROJ_LIB=$(PROJ_LIB) $(TESTCCT) $(CCTEXE) + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(TESTCCT) $(CCTEXE) check-local: testprojinfo-check test27-check test83-check testproj-check testvarious-check testdatumfile-check testign-check testntv2-check testcct-check diff --git a/test/cli/ntv2_out.dist b/test/cli/ntv2_out.dist index 531db7be..d90fdc37 100644 --- a/test/cli/ntv2_out.dist +++ b/test/cli/ntv2_out.dist @@ -13,6 +13,14 @@ Try with NTv2 and NTv1 together ... falls back to NTv1 Switching between NTv2 subgrids -112.5839956 49.4914451 0 -112.58307487 49.49145197 0.00000000 ############################################################## +Interpolating very close (and sometimes a bit outside) to the edges a NTv2 subgrid (#209) +-115.58333333 51.25000000 0 -115.58228512 51.24997866 0.00000000 +-115.58333333 51.25000010 0 -115.58228512 51.24997876 0.00000000 +-115.58333334 51.25000000 0 -115.58228513 51.24997866 0.00000000 +-115.49166667 51.07500000 0 -115.49062909 51.07497666 0.00000000 +-115.49166668 51.07500000 0 -115.49062910 51.07497666 0.00000000 +-115.49166667 51.07499990 0 -115.49062909 51.07497656 0.00000000 +############################################################## Attempt first with ntv2_0.gsb and then conus -111.5 45.26 -111.50079772 45.25992835 0.00000000 ############################################################## diff --git a/test/cli/td_out.dist b/test/cli/td_out.dist index 478a0d84..cf4b8d73 100644 --- a/test/cli/td_out.dist +++ b/test/cli/td_out.dist @@ -21,7 +21,7 @@ edge or even a wee bit outside (#141). -5.5001 52.0 -5.500100000000 52.000000000000 0.000000000000 -5.5 52.0 -5.498893534472 52.000109529716 0.000000000000 -5.5000000000001 52.0000000000001 -5.498893534472 52.000109529717 0.000000000000 --5.4999 51.9999 -5.498793541695 52.000009529743 0.000000000000 +-5.4999 51.9999 -5.498793593803 52.000009531513 0.000000000000 -5.5001 52.0 -5.500100000000 52.000000000000 0.000000000000 ############################################################## NAD27 -> NAD83: 1st through ntv1 or ntv2, 2nd through conus 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 e4b9ea2d..16e4bbc3 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 72a0f9a2..44ccac1e 100755 --- a/test/cli/testntv2 +++ b/test/cli/testntv2 @@ -61,11 +61,22 @@ $EXE +proj=latlong +datum=NAD83 +to +proj=latlong +ellps=clrk66 +nadgrids=ntv2_0 EOF echo "##############################################################" >> ${OUT} +echo "Interpolating very close (and sometimes a bit outside) to the edges a NTv2 subgrid (#209)" >> ${OUT} +$EXE +proj=latlong +datum=NAD83 +to +proj=latlong +ellps=clrk66 +nadgrids=ntv2_0.gsb -E -d 8 >>${OUT} <<EOF +-115.58333333 51.25000000 0 +-115.58333333 51.25000010 0 +-115.58333334 51.25000000 0 +-115.49166667 51.07500000 0 +-115.49166668 51.07500000 0 +-115.49166667 51.07499990 0 +EOF + +echo "##############################################################" >> ${OUT} echo Attempt first with ntv2_0.gsb and then conus >> ${OUT} $EXE +proj=longlat +datum=NAD27 +to +proj=longlat +datum=WGS84 -E -d 8 >>${OUT} <<EOF -111.5 45.26 EOF -# + echo "##############################################################" >> ${OUT} echo "NAD27 -> NAD83: 1st through ntv2, 2nd through conus" >> ${OUT} # diff --git a/test/gie/4D-API_cs2cs-style.gie b/test/gie/4D-API_cs2cs-style.gie index e5722b5e..3e4b9d2c 100644 --- a/test/gie/4D-API_cs2cs-style.gie +++ b/test/gie/4D-API_cs2cs-style.gie @@ -442,12 +442,24 @@ 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 ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- +Test null grid with vgridshift +------------------------------------------------------------------------------- +operation proj=vgridshift grids=tests/test_nodata.gtx,null ellps=GRS80 +------------------------------------------------------------------------------- +accept 4.05 52.1 0 +expect 4.05 52.1 -10 + +# Outside validity area of test_nodata.gtx. Fallback on null +accept 4.05 -52.1 0 +expect 4.05 -52.1 0 +------------------------------------------------------------------------------- + +------------------------------------------------------------------------------- Test bug fix of https://github.com/OSGeo/proj.4/issues/1025. Using geocent in the new API with a custom ellipsoid should return coordinates that correspond to that particular ellipsoid and not WGS84 as demonstrated in diff --git a/test/gie/Makefile.am b/test/gie/Makefile.am index 058b1fe9..560fea0b 100644 --- a/test/gie/Makefile.am +++ b/test/gie/Makefile.am @@ -9,35 +9,39 @@ 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/for_tests 4D-API-cs2cs-style: 4D-API_cs2cs-style.gie - PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< GDA: GDA.gie - PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< axisswap: axisswap.gie - PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< builtins: builtins.gie - PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< deformation: deformation.gie - PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< ellipsoid: ellipsoid.gie - PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< more_builtins: more_builtins.gie - PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< unitconvert: unitconvert.gie - PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< DHDN_ETRS89: DHDN_ETRS89.gie - PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES 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_SKIP_READ_USER_WRITABLE_DIRECTORY=YES 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/deformation.gie b/test/gie/deformation.gie index 37f62e6e..848b9e89 100644 --- a/test/gie/deformation.gie +++ b/test/gie/deformation.gie @@ -12,6 +12,35 @@ The input coordinate is located at lon=60, lam=-160 - somewhere in Alaska. <gie> ------------------------------------------------------------------------------- +Test with an extract of nkgrf03vel_realigned with ctable2+gtx +------------------------------------------------------------------------------- +operation +proj=pipeline + +step +proj=cart +ellps=GRS80 + +step +proj=deformation + +xy_grids=tests/nkgrf03vel_realigned_xy_extract.ct2 + +z_grids=tests/nkgrf03vel_realigned_z_extract.gtx +ellps=GRS80 +dt=1 + +step +proj=cart +ellps=GRS80 +inv +------------------------------------------------------------------------------- +tolerance 0.05 mm +accept 21.5 63 0 +expect 21.5000000049 62.9999999937 0.0083 +roundtrip 5 + +------------------------------------------------------------------------------- +Test with an extract of nkgrf03vel_realigned with GeoTIFF +------------------------------------------------------------------------------- +operation +proj=pipeline + +step +proj=cart +ellps=GRS80 + +step +proj=deformation + +grids=tests/nkgrf03vel_realigned_extract.tif +ellps=GRS80 +dt=1 + +step +proj=cart +ellps=GRS80 +inv +------------------------------------------------------------------------------- +tolerance 0.05 mm +accept 21.5 63 0 +expect 21.5000000049 62.9999999937 0.0083 +roundtrip 5 + +------------------------------------------------------------------------------- Test the +dt parameter ------------------------------------------------------------------------------- operation +proj=deformation +xy_grids=alaska +z_grids=egm96_15.gtx diff --git a/test/gie/geotiff_grids.gie b/test/gie/geotiff_grids.gie new file mode 100644 index 00000000..62a5b16d --- /dev/null +++ b/test/gie/geotiff_grids.gie @@ -0,0 +1,324 @@ + +------------------------------------------------------------------------------- +=============================================================================== +Test GeoTIFF grids +=============================================================================== + +<gie> + +# Those first tests using +proj=vgridshift only test the capability of reading +# correctly a value from various formulations of GeoTIFF file, hence only the +# forward path is tested (reverse path is tested in other files) + +------------------------------------------------------------------------------- +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 +------------------------------------------------------------------------------- + +# Check a nested grid of a nested grid only based on spatial extent analysis +------------------------------------------------------------------------------- +operation +proj=hgridshift +grids=tests/test_hgrid_with_two_level_of_subgrids_no_grid_name.tif +------------------------------------------------------------------------------- +accept -45.0 22.5 +accept -44.9983333334 22.5013888889 + +# Check a nested grid of a nested grid only based on spatial extent analysis +------------------------------------------------------------------------------- +operation +proj=vgridshift +grids=tests/test_hgrid_with_two_level_of_subgrids_no_grid_name.tif +multiplier=1 +------------------------------------------------------------------------------- +accept -45.0 22.5 0 +accept -45.0 22.5 5 + +------------------------------------------------------------------------------- +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/gigs/Makefile.am b/test/gigs/Makefile.am index a66052db..436a6e89 100644 --- a/test/gigs/Makefile.am +++ b/test/gigs/Makefile.am @@ -23,54 +23,54 @@ EXTRA_DIST = \ PROJ_LIB ?= ../../data/for_tests 5101.1: 5101.1-jhs.gie - PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< 5101.2: 5101.2-jhs.gie - PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< 5101.3: 5101.3-jhs.gie - PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< 5101.4: 5101.4-jhs-etmerc.gie - PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< 5102.1: 5102.1.gie - PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< 5103.1: 5103.1.gie - PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< 5103.2: 5103.2.gie - PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< 5103.3: 5103.3.gie - PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< 5105.2: 5105.2.gie - PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< 5106: 5106.gie - PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< 5107: 5107.gie - PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< 5109: 5109.gie - PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< 5111.1: 5111.1.gie - PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< 5112: 5112.gie - PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< 5113: 5113.gie - PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< 5201: 5201.gie - PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< 5208: 5208.gie - PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) $(GIEEXE) $< check-local: 5101.1 5101.2 5101.3 5101.4 5102.1 5103.1 5103.2 5103.3 5105.2 5106 5107 5109 5111.1 5112 5113 5201 5208 diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 841d72b3..845d07e5 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -71,7 +71,7 @@ target_link_libraries(proj_pj_transform_test ${PROJ_LIBRARIES}) add_test(NAME proj_pj_transform_test COMMAND proj_pj_transform_test) set_property(TEST proj_pj_transform_test - PROPERTY ENVIRONMENT "PROJ_LIB=${PROJECT_BINARY_DIR}/data") + PROPERTY ENVIRONMENT "PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES;PROJ_LIB=${PROJECT_BINARY_DIR}/data/for_tests") add_executable(proj_errno_string_test @@ -82,7 +82,7 @@ target_link_libraries(proj_errno_string_test ${PROJ_LIBRARIES}) add_test(NAME proj_errno_string_test COMMAND proj_errno_string_test) set_property(TEST proj_errno_string_test - PROPERTY ENVIRONMENT "PROJ_LIB=${PROJECT_BINARY_DIR}/data") + PROPERTY ENVIRONMENT "PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES;PROJ_LIB=${PROJECT_BINARY_DIR}/data/for_tests") add_executable(proj_angular_io_test main.cpp @@ -92,7 +92,7 @@ target_link_libraries(proj_angular_io_test ${PROJ_LIBRARIES}) add_test(NAME proj_angular_io_test COMMAND proj_angular_io_test) set_property(TEST proj_angular_io_test - PROPERTY ENVIRONMENT "PROJ_LIB=${PROJECT_BINARY_DIR}/data") + PROPERTY ENVIRONMENT "PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES;PROJ_LIB=${PROJECT_BINARY_DIR}/data/for_tests") add_executable(proj_context_test main.cpp @@ -102,7 +102,7 @@ target_link_libraries(proj_context_test ${PROJ_LIBRARIES}) add_test(NAME proj_context_test COMMAND proj_context_test) set_property(TEST proj_context_test - PROPERTY ENVIRONMENT "PROJ_LIB=${PROJECT_BINARY_DIR}/data") + PROPERTY ENVIRONMENT "PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES;PROJ_LIB=${PROJECT_BINARY_DIR}/data/for_tests") if(MSVC AND BUILD_LIBPROJ_SHARED) # ph_phi2_test not compatible of a .dll build @@ -115,7 +115,7 @@ else() ${PROJ_LIBRARIES}) add_test(NAME pj_phi2_test COMMAND pj_phi2_test) set_property(TEST pj_phi2_test - PROPERTY ENVIRONMENT "PROJ_LIB=${PROJECT_BINARY_DIR}/data") + PROPERTY ENVIRONMENT "PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES;PROJ_LIB=${PROJECT_BINARY_DIR}/data/for_tests") endif() add_executable(proj_test_cpp_api @@ -128,14 +128,15 @@ add_executable(proj_test_cpp_api test_operation.cpp test_datum.cpp test_factory.cpp - test_c_api.cpp) + test_c_api.cpp + test_grids.cpp) target_link_libraries(proj_test_cpp_api GTest::gtest ${PROJ_LIBRARIES} ${SQLITE3_LIBRARY}) add_test(NAME proj_test_cpp_api COMMAND proj_test_cpp_api) set_property(TEST proj_test_cpp_api - PROPERTY ENVIRONMENT "PROJ_LIB=${PROJECT_BINARY_DIR}/data") + PROPERTY ENVIRONMENT "PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES;PROJ_LIB=${PROJECT_BINARY_DIR}/data/for_tests") add_executable(gie_self_tests @@ -146,4 +147,20 @@ target_link_libraries(gie_self_tests ${PROJ_LIBRARIES}) add_test(NAME gie_self_tests COMMAND gie_self_tests) set_property(TEST gie_self_tests - PROPERTY ENVIRONMENT "PROJ_LIB=${PROJECT_BINARY_DIR}/data") + PROPERTY ENVIRONMENT "PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES;PROJ_LIB=${PROJECT_BINARY_DIR}/data/for_tests") + + +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} + ${SQLITE3_LIBRARY}) +add_test(NAME test_network COMMAND test_network) +set_property(TEST test_network + PROPERTY ENVIRONMENT "PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES;PROJ_LIB=${PROJECT_BINARY_DIR}/data/for_tests;PROJ_SOURCE_DATA=${PROJECT_SOURCE_DIR}/data") diff --git a/test/unit/Makefile.am b/test/unit/Makefile.am index b7196828..11a6473c 100644 --- a/test/unit/Makefile.am +++ b/test/unit/Makefile.am @@ -17,12 +17,13 @@ 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@ pj_transform_test-check: pj_transform_test - PROJ_LIB=$(PROJ_LIB) ./pj_transform_test + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) ./pj_transform_test pj_phi2_test_SOURCES = pj_phi2_test.cpp main.cpp pj_phi2_test_LDADD = ../../src/libproj.la @GTEST_LIBS@ @@ -46,20 +47,27 @@ proj_context_test_SOURCES = proj_context_test.cpp main.cpp proj_context_test_LDADD = ../../src/libproj.la @GTEST_LIBS@ proj_context_test-check: proj_context_test - ./proj_context_test + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES ./proj_context_test -test_cpp_api_SOURCES = test_util.cpp test_common.cpp test_crs.cpp test_metadata.cpp test_io.cpp test_operation.cpp test_datum.cpp test_factory.cpp test_c_api.cpp main.cpp +test_cpp_api_SOURCES = test_util.cpp test_common.cpp test_crs.cpp test_metadata.cpp test_io.cpp test_operation.cpp test_datum.cpp test_factory.cpp test_c_api.cpp test_grids.cpp main.cpp test_cpp_api_LDADD = ../../src/libproj.la @GTEST_LIBS@ @SQLITE3_LIBS@ test_cpp_api-check: test_cpp_api - PROJ_LIB=$(PROJ_LIB) ./test_cpp_api + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) ./test_cpp_api gie_self_tests_SOURCES = gie_self_tests.cpp main.cpp gie_self_tests_LDADD = ../../src/libproj.la @GTEST_LIBS@ @SQLITE3_LIBS@ gie_self_tests-check: gie_self_tests - PROJ_LIB=$(PROJ_LIB) ./gie_self_tests + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES PROJ_LIB=$(PROJ_LIB) ./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@ @SQLITE3_LIBS@ @CURL_LIBS@ + +test_network-check: test_network + PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES 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/gie_self_tests.cpp b/test/unit/gie_self_tests.cpp index a738db75..bcf31139 100644 --- a/test/unit/gie_self_tests.cpp +++ b/test/unit/gie_self_tests.cpp @@ -348,7 +348,9 @@ TEST(gie, info_functions) { /* proj_info() */ /* this one is difficult to test, since the output changes with the setup */ + putenv(const_cast<char *>("PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=")); info = proj_info(); + putenv(const_cast<char *>("PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES")); if (info.version[0] != '\0') { char tmpstr[64]; @@ -360,6 +362,9 @@ TEST(gie, info_functions) { ASSERT_NE(std::string(info.searchpath), std::string()); } + ASSERT_TRUE(std::string(info.searchpath).find("/proj") != + std::string::npos); + /* proj_pj_info() */ { P = proj_create(PJ_DEFAULT_CTX, diff --git a/test/unit/pj_transform_test.cpp b/test/unit/pj_transform_test.cpp index 1f4473c1..ddb054f0 100644 --- a/test/unit/pj_transform_test.cpp +++ b/test/unit/pj_transform_test.cpp @@ -616,6 +616,22 @@ TEST(proj_api_h, pj_set_finder) { // --------------------------------------------------------------------------- +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); +} + +// --------------------------------------------------------------------------- + TEST(pj_transform_test, ob_tran_to_meter_as_dest) { auto src = pj_init_plus( "+ellps=WGS84 +a=57.29577951308232 +proj=eqc +lon_0=0.0 +no_defs"); @@ -633,6 +649,79 @@ TEST(pj_transform_test, ob_tran_to_meter_as_dest) { // --------------------------------------------------------------------------- +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); +} + TEST(pj_transform_test, ob_tran_to_meter_as_srouce) { auto src = pj_init_plus("+ellps=WGS84 +proj=ob_tran +o_proj=latlon " "+o_lon_p=0.0 +o_lat_p=90.0 +lon_0=360.0 " diff --git a/test/unit/proj_context_test.cpp b/test/unit/proj_context_test.cpp index 23c46f29..92f251cd 100644 --- a/test/unit/proj_context_test.cpp +++ b/test/unit/proj_context_test.cpp @@ -40,7 +40,20 @@ namespace { -static std::string createTempDict(std::string &dirname) { +static bool createTmpFile(const std::string &filename) { + FILE *f = fopen(filename.c_str(), "wt"); + if (!f) + return false; + fprintf( + f, + "<MY_PIPELINE> +proj=pipeline +step +proj=utm +zone=31 +ellps=GRS80\n"); + fclose(f); + return true; +} + +// --------------------------------------------------------------------------- + +static std::string createTempDict(std::string &dirname, const char *filename) { const char *temp_dir = getenv("TEMP"); if (!temp_dir) { temp_dir = getenv("TMP"); @@ -58,16 +71,9 @@ static std::string createTempDict(std::string &dirname) { std::string tmpFilename; tmpFilename = temp_dir; tmpFilename += DIR_CHAR; - tmpFilename += "temp_proj_dic"; + tmpFilename += filename; - FILE *f = fopen(tmpFilename.c_str(), "wt"); - if (!f) - return std::string(); - fprintf( - f, - "<MY_PIPELINE> +proj=pipeline +step +proj=utm +zone=31 +ellps=GRS80\n"); - fclose(f); - return tmpFilename; + return createTmpFile(tmpFilename) ? tmpFilename : std::string(); } // --------------------------------------------------------------------------- @@ -85,7 +91,7 @@ static int MyUnlink(const std::string &filename) { TEST(proj_context, proj_context_set_file_finder) { std::string dirname; - auto filename = createTempDict(dirname); + auto filename = createTempDict(dirname, "temp_proj_dic1"); if (filename.empty()) return; @@ -111,7 +117,7 @@ TEST(proj_context, proj_context_set_file_finder) { finderData.dirname = dirname; proj_context_set_file_finder(ctx, finder, &finderData); - auto P = proj_create(ctx, "+init=temp_proj_dic:MY_PIPELINE"); + auto P = proj_create(ctx, "+init=temp_proj_dic1:MY_PIPELINE"); EXPECT_NE(P, nullptr); proj_destroy(P); @@ -125,7 +131,7 @@ TEST(proj_context, proj_context_set_file_finder) { TEST(proj_context, proj_context_set_search_paths) { std::string dirname; - auto filename = createTempDict(dirname); + auto filename = createTempDict(dirname, "temp_proj_dic2"); if (filename.empty()) return; @@ -134,7 +140,7 @@ TEST(proj_context, proj_context_set_search_paths) { const char *path = dirname.c_str(); proj_context_set_search_paths(ctx, 1, &path); - auto P = proj_create(ctx, "+init=temp_proj_dic:MY_PIPELINE"); + auto P = proj_create(ctx, "+init=temp_proj_dic2:MY_PIPELINE"); EXPECT_NE(P, nullptr); proj_destroy(P); @@ -143,4 +149,33 @@ TEST(proj_context, proj_context_set_search_paths) { MyUnlink(filename); } +// --------------------------------------------------------------------------- + +TEST(proj_context, read_grid_from_user_writable_directory) { + + auto ctx = proj_context_create(); + auto path = pj_context_get_user_writable_directory(ctx, true); + EXPECT_TRUE(!path.empty()); + auto filename = path + DIR_CHAR + "temp_proj_dic3"; + EXPECT_TRUE(createTmpFile(filename)); + { + // Check that with PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES (set by + // calling script), we cannot find the file + auto P = proj_create(ctx, "+init=temp_proj_dic3:MY_PIPELINE"); + EXPECT_EQ(P, nullptr); + proj_destroy(P); + } + { + // Cancel the effect of PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY + putenv(const_cast<char *>("PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=")); + auto P = proj_create(ctx, "+init=temp_proj_dic3:MY_PIPELINE"); + EXPECT_NE(P, nullptr); + putenv( + const_cast<char *>("PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES")); + proj_destroy(P); + } + proj_context_destroy(ctx); + MyUnlink(filename); +} + } // namespace diff --git a/test/unit/test_c_api.cpp b/test/unit/test_c_api.cpp index a657c61e..5c6dabba 100644 --- a/test/unit/test_c_api.cpp +++ b/test/unit/test_c_api.cpp @@ -4467,6 +4467,27 @@ TEST_F(CApi, proj_create_derived_geographic_crs) { // --------------------------------------------------------------------------- +TEST_F(CApi, proj_context_set_sqlite3_vfs_name) { + + PJ_CONTEXT *ctx = proj_context_create(); + proj_log_func(ctx, nullptr, [](void *, int, const char *) -> void {}); + + // Set a dummy VFS and check it is taken into account + // (failure to open proj.db) + proj_context_set_sqlite3_vfs_name(ctx, "dummy_vfs_name"); + ASSERT_EQ(proj_create(ctx, "EPSG:4326"), nullptr); + + // Restore default VFS + proj_context_set_sqlite3_vfs_name(ctx, nullptr); + PJ *crs_4326 = proj_create(ctx, "EPSG:4326"); + ASSERT_NE(crs_4326, nullptr); + proj_destroy(crs_4326); + + proj_context_destroy(ctx); +} + +// --------------------------------------------------------------------------- + TEST_F(CApi, proj_is_equivalent_to_with_ctx) { auto from_epsg = proj_create_from_database(m_ctxt, "EPSG", "7844", PJ_CATEGORY_CRS, false, nullptr); diff --git a/test/unit/test_factory.cpp b/test/unit/test_factory.cpp index 8ae58c96..6a88e2b6 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_grids.cpp b/test/unit/test_grids.cpp new file mode 100644 index 00000000..5240949e --- /dev/null +++ b/test/unit/test_grids.cpp @@ -0,0 +1,227 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: Test grids.hpp + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2020, 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 "grids.hpp" + +#include "proj_internal.h" // M_PI + +namespace { + +// --------------------------------------------------------------------------- + +class GridTest : public ::testing::Test { + + static void DummyLogFunction(void *, int, const char *) {} + + protected: + void SetUp() override { + m_ctxt = proj_context_create(); + proj_log_func(m_ctxt, nullptr, DummyLogFunction); + m_ctxt2 = proj_context_create(); + proj_log_func(m_ctxt2, nullptr, DummyLogFunction); + } + + void TearDown() override { + proj_context_destroy(m_ctxt); + proj_context_destroy(m_ctxt2); + } + + PJ_CONTEXT *m_ctxt = nullptr; + PJ_CONTEXT *m_ctxt2 = nullptr; +}; + +// --------------------------------------------------------------------------- + +TEST_F(GridTest, VerticalShiftGridSet_null) { + auto gridSet = NS_PROJ::VerticalShiftGridSet::open(m_ctxt, "null"); + ASSERT_NE(gridSet, nullptr); + auto grid = gridSet->gridAt(0.0, 0.0); + ASSERT_NE(grid, nullptr); + EXPECT_EQ(grid->width(), 3); + EXPECT_EQ(grid->height(), 3); + EXPECT_EQ(grid->extentAndRes().westLon, -M_PI); + EXPECT_TRUE(grid->isNullGrid()); + EXPECT_FALSE(grid->hasChanged()); + float out = -1.0f; + EXPECT_TRUE(grid->valueAt(0, 0, out)); + EXPECT_EQ(out, 0.0f); + EXPECT_FALSE(grid->isNodata(0.0f, 0.0)); + gridSet->reassign_context(m_ctxt2); + gridSet->reopen(m_ctxt2); +} + +// --------------------------------------------------------------------------- + +TEST_F(GridTest, VerticalShiftGridSet_gtx) { + ASSERT_EQ(NS_PROJ::VerticalShiftGridSet::open(m_ctxt, "foobar"), nullptr); + auto gridSet = + NS_PROJ::VerticalShiftGridSet::open(m_ctxt, "tests/test_nodata.gtx"); + ASSERT_NE(gridSet, nullptr); + ASSERT_EQ(gridSet->gridAt(-100, -100), nullptr); + auto grid = gridSet->gridAt(4.15 / 180 * M_PI, 52.15 / 180 * M_PI); + ASSERT_NE(grid, nullptr); + EXPECT_TRUE(grid->isNodata(-88.8888f, 1.0)); + gridSet->reassign_context(m_ctxt2); + gridSet->reopen(m_ctxt2); + grid = gridSet->gridAt(4.15 / 180 * M_PI, 52.15 / 180 * M_PI); + EXPECT_NE(grid, nullptr); +} + +// --------------------------------------------------------------------------- + +TEST_F(GridTest, HorizontalShiftGridSet_null) { + auto gridSet = NS_PROJ::HorizontalShiftGridSet::open(m_ctxt, "null"); + ASSERT_NE(gridSet, nullptr); + auto grid = gridSet->gridAt(0.0, 0.0); + ASSERT_NE(grid, nullptr); + EXPECT_EQ(grid->width(), 3); + EXPECT_EQ(grid->height(), 3); + EXPECT_EQ(grid->extentAndRes().westLon, -M_PI); + EXPECT_TRUE(grid->isNullGrid()); + EXPECT_FALSE(grid->hasChanged()); + float out1 = -1.0f; + float out2 = -1.0f; + EXPECT_TRUE(grid->valueAt(0, 0, false, out1, out2)); + EXPECT_EQ(out1, 0.0f); + EXPECT_EQ(out2, 0.0f); + gridSet->reassign_context(m_ctxt2); + gridSet->reopen(m_ctxt2); +} + +// --------------------------------------------------------------------------- + +TEST_F(GridTest, HorizontalShiftGridSet_gtiff) { + auto gridSet = + NS_PROJ::HorizontalShiftGridSet::open(m_ctxt, "tests/test_hgrid.tif"); + ASSERT_NE(gridSet, nullptr); + EXPECT_EQ(gridSet->format(), "gtiff"); + EXPECT_TRUE(gridSet->name().find("tests/test_hgrid.tif") != + std::string::npos) + << gridSet->name(); + EXPECT_EQ(gridSet->grids().size(), 1U); + ASSERT_EQ(gridSet->gridAt(-100, -100), nullptr); + auto grid = gridSet->gridAt(5.5 / 180 * M_PI, 53.5 / 180 * M_PI); + ASSERT_NE(grid, nullptr); + EXPECT_EQ(grid->width(), 4); + EXPECT_EQ(grid->height(), 4); + EXPECT_EQ(grid->extentAndRes().westLon, 4.0 / 180 * M_PI); + EXPECT_FALSE(grid->isNullGrid()); + EXPECT_FALSE(grid->hasChanged()); + float out1 = -1.0f; + float out2 = -1.0f; + EXPECT_TRUE(grid->valueAt(0, 3, false, out1, out2)); + EXPECT_EQ(out1, static_cast<float>(14400.0 / 3600. / 180 * M_PI)); + EXPECT_EQ(out2, static_cast<float>(900.0 / 3600. / 180 * M_PI)); + gridSet->reassign_context(m_ctxt2); + gridSet->reopen(m_ctxt2); + grid = gridSet->gridAt(5.5 / 180 * M_PI, 53.5 / 180 * M_PI); + EXPECT_NE(grid, nullptr); +} + +// --------------------------------------------------------------------------- + +TEST_F(GridTest, GenericShiftGridSet_null) { + auto gridSet = NS_PROJ::GenericShiftGridSet::open(m_ctxt, "null"); + ASSERT_NE(gridSet, nullptr); + auto grid = gridSet->gridAt(0.0, 0.0); + ASSERT_NE(grid, nullptr); + EXPECT_EQ(grid->width(), 3); + EXPECT_EQ(grid->height(), 3); + EXPECT_EQ(grid->extentAndRes().westLon, -M_PI); + EXPECT_TRUE(grid->isNullGrid()); + EXPECT_FALSE(grid->hasChanged()); + float out = -1.0f; + EXPECT_TRUE(grid->valueAt(0, 0, 0, out)); + EXPECT_EQ(out, 0.0f); + EXPECT_EQ(grid->unit(0), ""); + EXPECT_EQ(grid->description(0), ""); + EXPECT_EQ(grid->metadataItem("foo"), ""); + EXPECT_EQ(grid->samplesPerPixel(), 0); + gridSet->reassign_context(m_ctxt2); + gridSet->reopen(m_ctxt2); +} + +// --------------------------------------------------------------------------- + +TEST_F(GridTest, GenericShiftGridSet_gtiff) { + ASSERT_EQ(NS_PROJ::GenericShiftGridSet::open(m_ctxt, "foobar"), nullptr); + auto gridSet = NS_PROJ::GenericShiftGridSet::open( + m_ctxt, "tests/nkgrf03vel_realigned_extract.tif"); + ASSERT_NE(gridSet, nullptr); + ASSERT_EQ(gridSet->gridAt(-100, -100), nullptr); + auto grid = gridSet->gridAt(21.3333333 / 180 * M_PI, 63.0 / 180 * M_PI); + ASSERT_NE(grid, nullptr); + EXPECT_EQ(grid->width(), 5); + EXPECT_EQ(grid->height(), 5); + EXPECT_EQ(grid->extentAndRes().westLon, 21.0 / 180 * M_PI); + EXPECT_FALSE(grid->isNullGrid()); + EXPECT_FALSE(grid->hasChanged()); + float out = -1.0f; + EXPECT_FALSE(grid->valueAt(0, 0, grid->samplesPerPixel(), out)); + EXPECT_EQ(grid->metadataItem("area_of_use"), "Nordic and Baltic countries"); + EXPECT_EQ(grid->metadataItem("non_existing"), std::string()); + EXPECT_EQ(grid->metadataItem("non_existing", 1), std::string()); + EXPECT_EQ(grid->metadataItem("non_existing", 10), std::string()); + gridSet->reassign_context(m_ctxt2); + gridSet->reopen(m_ctxt2); + grid = gridSet->gridAt(21.3333333 / 180 * M_PI, 63.0 / 180 * M_PI); + EXPECT_NE(grid, nullptr); +} + +// --------------------------------------------------------------------------- + +TEST_F(GridTest, GenericShiftGridSet_gtiff_with_subgrid) { + auto gridSet = NS_PROJ::GenericShiftGridSet::open( + m_ctxt, "tests/test_hgrid_with_subgrid.tif"); + ASSERT_NE(gridSet, nullptr); + ASSERT_EQ(gridSet->gridAt(-100, -100), nullptr); + auto grid = + gridSet->gridAt(-115.5416667 / 180 * M_PI, 51.1666667 / 180 * M_PI); + ASSERT_NE(grid, nullptr); + EXPECT_EQ(grid->width(), 11); + EXPECT_EQ(grid->height(), 21); + EXPECT_EQ(grid->metadataItem("grid_name"), "ALbanff"); +} + +// --------------------------------------------------------------------------- + +TEST_F(GridTest, + GenericShiftGridSet_gtiff_with_two_level_of_subgrids_no_grid_name) { + auto gridSet = NS_PROJ::GenericShiftGridSet::open( + m_ctxt, "tests/test_hgrid_with_two_level_of_subgrids_no_grid_name.tif"); + ASSERT_NE(gridSet, nullptr); + ASSERT_EQ(gridSet->gridAt(-100, -100), nullptr); + auto grid = gridSet->gridAt(-45.5 / 180 * M_PI, 22.5 / 180 * M_PI); + ASSERT_NE(grid, nullptr); + EXPECT_EQ(grid->width(), 8); + EXPECT_EQ(grid->height(), 8); +} + +} // namespace diff --git a/test/unit/test_network.cpp b/test/unit/test_network.cpp new file mode 100644 index 00000000..241e6bbc --- /dev/null +++ b/test/unit/test_network.cpp @@ -0,0 +1,1856 @@ +/****************************************************************************** + * + * 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> + +#include <sqlite3.h> +#include <time.h> + +#ifdef _WIN32 +#include <windows.h> +#else +#include <unistd.h> +#endif + +#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_grid_cache_set_enable(ctx, false); + 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(); + proj_grid_cache_set_enable(ctx, false); + 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_grid_cache_set_enable(ctx, false); + 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_grid_cache_set_enable(ctx, false); + 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_grid_cache_set_enable(ctx, false); + 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_grid_cache_set_enable(ctx, false); + 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 = "bytes=0-16383/10000000"; + event->file_id = 1; + exchange.events.emplace_back(std::move(event)); + } + { + std::unique_ptr<GetHeaderValueEvent> event(new GetHeaderValueEvent()); + event->ctx = ctx; + event->key = "Last-Modified"; + event->value = "some_date"; + event->file_id = 1; + exchange.events.emplace_back(std::move(event)); + } + { + std::unique_ptr<GetHeaderValueEvent> event(new GetHeaderValueEvent()); + event->ctx = ctx; + event->key = "ETag"; + event->value = "some_etag"; + 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)); + } + { + std::unique_ptr<GetHeaderValueEvent> event(new GetHeaderValueEvent()); + event->ctx = ctx; + event->key = "Content-Range"; + event->value = "bytes=0-16383/10000000"; + event->file_id = 2; + exchange.events.emplace_back(std::move(event)); + } + { + std::unique_ptr<GetHeaderValueEvent> event(new GetHeaderValueEvent()); + event->ctx = ctx; + event->key = "Last-Modified"; + event->value = "some_date"; + event->file_id = 2; + exchange.events.emplace_back(std::move(event)); + } + { + std::unique_ptr<GetHeaderValueEvent> event(new GetHeaderValueEvent()); + event->ctx = ctx; + event->key = "ETag"; + event->value = "some_etag"; + 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; + 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)); + } + { + std::unique_ptr<GetHeaderValueEvent> event(new GetHeaderValueEvent()); + event->ctx = ctx; + event->key = "Content-Range"; + event->value = "bytes=0-16383/10000000"; + event->file_id = 2; + exchange.events.emplace_back(std::move(event)); + } + { + std::unique_ptr<GetHeaderValueEvent> event(new GetHeaderValueEvent()); + event->ctx = ctx; + event->key = "Last-Modified"; + event->value = "some_date"; + event->file_id = 2; + exchange.events.emplace_back(std::move(event)); + } + { + std::unique_ptr<GetHeaderValueEvent> event(new GetHeaderValueEvent()); + event->ctx = ctx; + event->key = "ETag"; + event->value = "some_etag"; + 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; + 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_grid_cache_set_enable(ctx, false); + 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<GetHeaderValueEvent> event(new GetHeaderValueEvent()); + event->ctx = ctx; + event->key = "Last-Modified"; + event->value = "some_date"; + event->file_id = 1; + exchange.events.emplace_back(std::move(event)); + } + { + std::unique_ptr<GetHeaderValueEvent> event(new GetHeaderValueEvent()); + event->ctx = ctx; + event->key = "ETag"; + event->value = "some_etag"; + 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_grid_cache_set_enable(ctx, false); + 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_grid_cache_set_enable(ctx, false); + 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 = "bytes=0-16383/10000000"; + event->file_id = 1; + exchange.events.emplace_back(std::move(event)); + } + { + std::unique_ptr<GetHeaderValueEvent> event(new GetHeaderValueEvent()); + event->ctx = ctx; + event->key = "Last-Modified"; + event->value = "some_date"; + event->file_id = 1; + exchange.events.emplace_back(std::move(event)); + } + { + std::unique_ptr<GetHeaderValueEvent> event(new GetHeaderValueEvent()); + event->ctx = ctx; + event->key = "ETag"; + event->value = "some_etag"; + 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)); + } + { + std::unique_ptr<GetHeaderValueEvent> event(new GetHeaderValueEvent()); + event->ctx = ctx; + event->key = "Content-Range"; + event->value = "bytes=0-16383/10000000"; + event->file_id = 2; + exchange.events.emplace_back(std::move(event)); + } + { + std::unique_ptr<GetHeaderValueEvent> event(new GetHeaderValueEvent()); + event->ctx = ctx; + event->key = "Last-Modified"; + event->value = "some_date"; + event->file_id = 2; + exchange.events.emplace_back(std::move(event)); + } + { + std::unique_ptr<GetHeaderValueEvent> event(new GetHeaderValueEvent()); + event->ctx = ctx; + event->key = "ETag"; + event->value = "some_etag"; + 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; + 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); +} + +// --------------------------------------------------------------------------- + +TEST(networking, simul_file_change_while_opened) { + auto ctx = proj_context_create(); + proj_grid_cache_set_enable(ctx, false); + 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/file_change_while_opened.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 = "bytes=0-16383/10000000"; + event->file_id = 1; + exchange.events.emplace_back(std::move(event)); + } + { + std::unique_ptr<GetHeaderValueEvent> event(new GetHeaderValueEvent()); + event->ctx = ctx; + event->key = "Last-Modified"; + event->value = "some_date"; + event->file_id = 1; + exchange.events.emplace_back(std::move(event)); + } + { + std::unique_ptr<GetHeaderValueEvent> event(new GetHeaderValueEvent()); + event->ctx = ctx; + event->key = "ETag"; + event->value = "some_etag"; + 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/file_change_while_opened.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/file_change_while_opened.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)); + } + { + std::unique_ptr<GetHeaderValueEvent> event(new GetHeaderValueEvent()); + event->ctx = ctx; + event->key = "Content-Range"; + event->value = "bytes=0-16383/10000000"; + event->file_id = 2; + exchange.events.emplace_back(std::move(event)); + } + { + std::unique_ptr<GetHeaderValueEvent> event(new GetHeaderValueEvent()); + event->ctx = ctx; + event->key = "Last-Modified"; + event->value = "some_date CHANGED!!!!"; + event->file_id = 2; + exchange.events.emplace_back(std::move(event)); + } + { + std::unique_ptr<GetHeaderValueEvent> event(new GetHeaderValueEvent()); + event->ctx = ctx; + event->key = "ETag"; + event->value = "some_etag"; + event->file_id = 2; + exchange.events.emplace_back(std::move(event)); + } + { + std::unique_ptr<CloseEvent> event(new CloseEvent()); + event->ctx = ctx; + event->file_id = 2; + exchange.events.emplace_back(std::move(event)); + } + { + std::unique_ptr<OpenEvent> event(new OpenEvent()); + event->ctx = ctx; + event->url = "https://foo/file_change_while_opened.tif"; + event->offset = 0; + event->size_to_read = 16384; + event->response.resize(16384); + event->file_id = 3; + + 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 = "bytes=0-16383/10000000"; + event->file_id = 3; + exchange.events.emplace_back(std::move(event)); + } + { + std::unique_ptr<GetHeaderValueEvent> event(new GetHeaderValueEvent()); + event->ctx = ctx; + event->key = "Last-Modified"; + event->value = "some_date CHANGED!!!!"; + event->file_id = 3; + exchange.events.emplace_back(std::move(event)); + } + { + std::unique_ptr<GetHeaderValueEvent> event(new GetHeaderValueEvent()); + event->ctx = ctx; + event->key = "ETag"; + event->value = "some_etag"; + event->file_id = 3; + 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<CloseEvent> event(new CloseEvent()); + event->ctx = ctx; + event->file_id = 3; + 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_grid_cache_set_enable(ctx, false); + 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_grid_cache_set_enable(ctx, false); + 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_grid_cache_set_enable(ctx, false); + 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_grid_cache_set_enable(ctx, false); + 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_grid_cache_set_enable(ctx, false); + 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 + +// --------------------------------------------------------------------------- + +#ifdef CURL_ENABLED + +static PROJ_NETWORK_HANDLE *dummy_open_cbk(PJ_CONTEXT *, const char *, + unsigned long long, size_t, void *, + size_t *, size_t, char *, void *) { + assert(false); + return nullptr; +} + +static void dummy_close_cbk(PJ_CONTEXT *, PROJ_NETWORK_HANDLE *, void *) { + assert(false); +} + +static const char *dummy_get_header_value_cbk(PJ_CONTEXT *, + PROJ_NETWORK_HANDLE *, + const char *, void *) { + assert(false); + return nullptr; +} + +static size_t dummy_read_range_cbk(PJ_CONTEXT *, PROJ_NETWORK_HANDLE *, + unsigned long long, size_t, void *, size_t, + char *, void *) { + assert(false); + return 0; +} + +TEST(networking, cache_basic) { + if (!networkAccessOK) { + return; + } + + proj_cleanup(); + + 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"; + + auto ctx = proj_context_create(); + proj_context_set_enable_network(ctx, true); + + auto P = proj_create(ctx, pipeline); + ASSERT_NE(P, nullptr); + proj_destroy(P); + + EXPECT_TRUE(!pj_context_get_grid_cache_filename(ctx).empty()); + + sqlite3 *hDB = nullptr; + sqlite3_open_v2(pj_context_get_grid_cache_filename(ctx).c_str(), &hDB, + SQLITE_OPEN_READONLY, nullptr); + ASSERT_NE(hDB, nullptr); + sqlite3_stmt *hStmt = nullptr; + sqlite3_prepare_v2(hDB, "SELECT url, offset FROM chunks WHERE id = (" + "SELECT chunk_id FROM linked_chunks WHERE id = (" + "SELECT head FROM linked_chunks_head_tail))", + -1, &hStmt, nullptr); + ASSERT_NE(hStmt, nullptr); + ASSERT_EQ(sqlite3_step(hStmt), SQLITE_ROW); + const char *url = + reinterpret_cast<const char *>(sqlite3_column_text(hStmt, 0)); + ASSERT_NE(url, nullptr); + ASSERT_EQ(std::string(url), "https://cdn.proj.org/ntf_r93.tif"); + ASSERT_EQ(sqlite3_column_int64(hStmt, 1), 0); + sqlite3_finalize(hStmt); + sqlite3_close(hDB); + + proj_cleanup(); + + // Check that a second access doesn't trigger any network activity + ASSERT_TRUE(proj_context_set_network_callbacks( + ctx, dummy_open_cbk, dummy_close_cbk, dummy_get_header_value_cbk, + dummy_read_range_cbk, nullptr)); + P = proj_create(ctx, pipeline); + ASSERT_NE(P, nullptr); + proj_destroy(P); + + proj_context_destroy(ctx); +} + +// --------------------------------------------------------------------------- + +TEST(networking, proj_grid_cache_clear) { + if (!networkAccessOK) { + return; + } + 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"; + + proj_cleanup(); + + auto ctx = proj_context_create(); + proj_context_set_enable_network(ctx, true); + proj_grid_cache_set_filename(ctx, "tmp_proj_db_cache.db"); + EXPECT_EQ(pj_context_get_grid_cache_filename(ctx), + std::string("tmp_proj_db_cache.db")); + + proj_grid_cache_clear(ctx); + + auto P = proj_create(ctx, pipeline); + ASSERT_NE(P, nullptr); + proj_destroy(P); + + // Check that the file exists + { + sqlite3 *hDB = nullptr; + ASSERT_EQ( + sqlite3_open_v2(pj_context_get_grid_cache_filename(ctx).c_str(), + &hDB, SQLITE_OPEN_READONLY, nullptr), + SQLITE_OK); + sqlite3_close(hDB); + } + + proj_grid_cache_clear(ctx); + + // Check that the file no longer exists + { + sqlite3 *hDB = nullptr; + ASSERT_NE( + sqlite3_open_v2(pj_context_get_grid_cache_filename(ctx).c_str(), + &hDB, SQLITE_OPEN_READONLY, nullptr), + SQLITE_OK); + sqlite3_close(hDB); + } + + proj_context_destroy(ctx); +} + +// --------------------------------------------------------------------------- + +TEST(networking, cache_saturation) { + if (!networkAccessOK) { + return; + } + 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"; + + proj_cleanup(); + + auto ctx = proj_context_create(); + proj_context_set_enable_network(ctx, true); + proj_grid_cache_set_filename(ctx, "tmp_proj_db_cache.db"); + + proj_grid_cache_clear(ctx); + + // Limit to two chunks + putenv(const_cast<char *>("PROJ_GRID_CACHE_MAX_SIZE_BYTES=32768")); + proj_grid_cache_set_max_size(ctx, 0); + putenv(const_cast<char *>("PROJ_GRID_CACHE_MAX_SIZE_BYTES=")); + + auto P = proj_create(ctx, pipeline); + ASSERT_NE(P, nullptr); + + 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); + + sqlite3 *hDB = nullptr; + sqlite3_open_v2(pj_context_get_grid_cache_filename(ctx).c_str(), &hDB, + SQLITE_OPEN_READONLY, nullptr); + ASSERT_NE(hDB, nullptr); + + sqlite3_stmt *hStmt = nullptr; + sqlite3_prepare_v2(hDB, "SELECT COUNT(*) FROM chunk_data UNION ALL " + "SELECT COUNT(*) FROM chunks UNION ALL " + "SELECT COUNT(*) FROM linked_chunks", + -1, &hStmt, nullptr); + ASSERT_NE(hStmt, nullptr); + ASSERT_EQ(sqlite3_step(hStmt), SQLITE_ROW); + ASSERT_EQ(sqlite3_column_int64(hStmt, 0), 2); + ASSERT_EQ(sqlite3_step(hStmt), SQLITE_ROW); + ASSERT_EQ(sqlite3_column_int64(hStmt, 0), 2); + ASSERT_EQ(sqlite3_step(hStmt), SQLITE_ROW); + ASSERT_EQ(sqlite3_column_int64(hStmt, 0), 2); + sqlite3_finalize(hStmt); + sqlite3_close(hDB); + + proj_grid_cache_clear(ctx); + + proj_context_destroy(ctx); +} + +// --------------------------------------------------------------------------- + +TEST(networking, cache_ttl) { + if (!networkAccessOK) { + return; + } + 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"; + + proj_cleanup(); + + auto ctx = proj_context_create(); + proj_context_set_enable_network(ctx, true); + proj_grid_cache_set_filename(ctx, "tmp_proj_db_cache.db"); + + proj_grid_cache_clear(ctx); + + auto P = proj_create(ctx, pipeline); + ASSERT_NE(P, nullptr); + + 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); + + sqlite3 *hDB = nullptr; + sqlite3_open_v2(pj_context_get_grid_cache_filename(ctx).c_str(), &hDB, + SQLITE_OPEN_READWRITE, nullptr); + ASSERT_NE(hDB, nullptr); + + // Force lastChecked to the Epoch so that data is expired. + sqlite3_stmt *hStmt = nullptr; + sqlite3_prepare_v2(hDB, "UPDATE properties SET lastChecked = 0, " + "lastModified = 'foo', etag = 'bar'", + -1, &hStmt, nullptr); + ASSERT_NE(hStmt, nullptr); + ASSERT_EQ(sqlite3_step(hStmt), SQLITE_DONE); + sqlite3_finalize(hStmt); + + // Put junk in already cached data to check that we will refresh it. + hStmt = nullptr; + sqlite3_prepare_v2(hDB, "UPDATE chunk_data SET data = zeroblob(16384)", -1, + &hStmt, nullptr); + ASSERT_NE(hStmt, nullptr); + ASSERT_EQ(sqlite3_step(hStmt), SQLITE_DONE); + sqlite3_finalize(hStmt); + sqlite3_close(hDB); + + proj_cleanup(); + + // Set a never expire ttl + proj_grid_cache_set_ttl(ctx, -1); + + // We'll get junk data, hence the pipeline initialization fails + proj_log_func(ctx, nullptr, silent_logger); + P = proj_create(ctx, pipeline); + ASSERT_EQ(P, nullptr); + proj_destroy(P); + + proj_cleanup(); + + // Set a normal ttl + proj_grid_cache_set_ttl(ctx, 86400); + + // Pipeline creation succeeds + P = proj_create(ctx, pipeline); + ASSERT_NE(P, nullptr); + proj_destroy(P); + + hDB = nullptr; + sqlite3_open_v2(pj_context_get_grid_cache_filename(ctx).c_str(), &hDB, + SQLITE_OPEN_READWRITE, nullptr); + ASSERT_NE(hDB, nullptr); + hStmt = nullptr; + sqlite3_prepare_v2(hDB, + "SELECT lastChecked, lastModified, etag FROM properties", + -1, &hStmt, nullptr); + ASSERT_NE(hStmt, nullptr); + ASSERT_EQ(sqlite3_step(hStmt), SQLITE_ROW); + ASSERT_NE(sqlite3_column_int64(hStmt, 0), 0); + ASSERT_NE(sqlite3_column_text(hStmt, 1), nullptr); + ASSERT_NE(std::string(reinterpret_cast<const char *>( + sqlite3_column_text(hStmt, 1))), + "foo"); + ASSERT_NE(sqlite3_column_text(hStmt, 2), nullptr); + ASSERT_NE(std::string(reinterpret_cast<const char *>( + sqlite3_column_text(hStmt, 2))), + "bar"); + sqlite3_finalize(hStmt); + sqlite3_close(hDB); + + proj_grid_cache_clear(ctx); + + proj_context_destroy(ctx); +} + +// --------------------------------------------------------------------------- + +TEST(networking, cache_lock) { + if (!networkAccessOK) { + return; + } + 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"; + + proj_cleanup(); + + auto ctx = proj_context_create(); + proj_context_set_enable_network(ctx, true); + proj_grid_cache_set_filename(ctx, "tmp_proj_db_cache.db"); + + proj_grid_cache_clear(ctx); + + auto P = proj_create(ctx, pipeline); + ASSERT_NE(P, nullptr); + + 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); + + // Take a lock + sqlite3 *hDB = nullptr; + sqlite3_open_v2(pj_context_get_grid_cache_filename(ctx).c_str(), &hDB, + SQLITE_OPEN_READWRITE, nullptr); + ASSERT_NE(hDB, nullptr); + sqlite3_stmt *hStmt = nullptr; + sqlite3_prepare_v2(hDB, "BEGIN EXCLUSIVE", -1, &hStmt, nullptr); + ASSERT_NE(hStmt, nullptr); + ASSERT_EQ(sqlite3_step(hStmt), SQLITE_DONE); + sqlite3_finalize(hStmt); + + proj_cleanup(); + + time_t start; + time(&start); + // 2 lock attempts, so we must sleep for each at least 0.5 ms + putenv(const_cast<char *>("PROJ_LOCK_MAX_ITERS=25")); + P = proj_create(ctx, pipeline); + putenv(const_cast<char *>("PROJ_LOCK_MAX_ITERS=")); + ASSERT_NE(P, nullptr); + proj_destroy(P); + + // Check that we have spend more than 1 sec + time_t end; + time(&end); + ASSERT_GE(end - start, 1U); + + sqlite3_close(hDB); + + proj_grid_cache_clear(ctx); + + proj_context_destroy(ctx); +} + +// --------------------------------------------------------------------------- + +TEST(networking, download_whole_files) { + if (!networkAccessOK) { + return; + } + + proj_cleanup(); + unlink("proj_test_tmp/cache.db"); + unlink("proj_test_tmp/dvr90.tif"); + rmdir("proj_test_tmp"); + + putenv(const_cast<char *>("PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=")); + putenv(const_cast<char *>("PROJ_USER_WRITABLE_DIRECTORY=./proj_test_tmp")); + putenv(const_cast<char *>("PROJ_FULL_FILE_CHUNK_SIZE=100000")); + auto ctx = proj_context_create(); + proj_context_set_enable_network(ctx, true); + + ASSERT_TRUE(proj_is_download_needed(ctx, "dvr90.gtx", false)); + + ASSERT_TRUE(proj_download_file(ctx, "dvr90.gtx", false, nullptr, nullptr)); + + FILE *f = fopen("proj_test_tmp/dvr90.tif", "rb"); + ASSERT_NE(f, nullptr); + fclose(f); + + proj_context_set_enable_network(ctx, false); + + const char *pipeline = + "+proj=pipeline " + "+step +proj=unitconvert +xy_in=deg +xy_out=rad " + "+step +proj=vgridshift +grids=dvr90.gtx +multiplier=1 " + "+step +proj=unitconvert +xy_in=rad +xy_out=deg"; + + auto P = proj_create(ctx, pipeline); + ASSERT_NE(P, nullptr); + + double lon = 12; + double lat = 56; + double z = 0; + proj_trans_generic(P, PJ_FWD, &lon, sizeof(double), 1, &lat, sizeof(double), + 1, &z, sizeof(double), 1, nullptr, 0, 0); + EXPECT_NEAR(z, 36.5909996032715, 1e-10); + proj_destroy(P); + + proj_context_set_enable_network(ctx, true); + + ASSERT_FALSE(proj_is_download_needed(ctx, "dvr90.gtx", false)); + + { + sqlite3 *hDB = nullptr; + sqlite3_open_v2("proj_test_tmp/cache.db", &hDB, SQLITE_OPEN_READWRITE, + nullptr); + ASSERT_NE(hDB, nullptr); + // Force lastChecked to the Epoch so that data is expired. + sqlite3_stmt *hStmt = nullptr; + sqlite3_prepare_v2( + hDB, "UPDATE downloaded_file_properties SET lastChecked = 0", -1, + &hStmt, nullptr); + ASSERT_NE(hStmt, nullptr); + ASSERT_EQ(sqlite3_step(hStmt), SQLITE_DONE); + sqlite3_finalize(hStmt); + sqlite3_close(hDB); + } + + // If we ignore TTL settings, then no network access will be done + ASSERT_FALSE(proj_is_download_needed(ctx, "dvr90.gtx", true)); + + { + sqlite3 *hDB = nullptr; + sqlite3_open_v2("proj_test_tmp/cache.db", &hDB, SQLITE_OPEN_READWRITE, + nullptr); + ASSERT_NE(hDB, nullptr); + // Check that the lastChecked timestamp is still 0 + sqlite3_stmt *hStmt = nullptr; + sqlite3_prepare_v2(hDB, + "SELECT lastChecked FROM downloaded_file_properties", + -1, &hStmt, nullptr); + ASSERT_NE(hStmt, nullptr); + ASSERT_EQ(sqlite3_step(hStmt), SQLITE_ROW); + ASSERT_EQ(sqlite3_column_int64(hStmt, 0), 0); + sqlite3_finalize(hStmt); + sqlite3_close(hDB); + } + + // Should recheck from the CDN, update last_checked and do nothing + ASSERT_FALSE(proj_is_download_needed(ctx, "dvr90.gtx", false)); + + { + sqlite3 *hDB = nullptr; + sqlite3_open_v2("proj_test_tmp/cache.db", &hDB, SQLITE_OPEN_READWRITE, + nullptr); + ASSERT_NE(hDB, nullptr); + sqlite3_stmt *hStmt = nullptr; + // Check that the lastChecked timestamp has been updated + sqlite3_prepare_v2(hDB, + "SELECT lastChecked FROM downloaded_file_properties", + -1, &hStmt, nullptr); + ASSERT_NE(hStmt, nullptr); + ASSERT_EQ(sqlite3_step(hStmt), SQLITE_ROW); + ASSERT_NE(sqlite3_column_int64(hStmt, 0), 0); + sqlite3_finalize(hStmt); + hStmt = nullptr; + + // Now invalid lastModified. This should trigger a new download + sqlite3_prepare_v2( + hDB, "UPDATE downloaded_file_properties SET lastChecked = 0, " + "lastModified = 'foo'", + -1, &hStmt, nullptr); + ASSERT_NE(hStmt, nullptr); + ASSERT_EQ(sqlite3_step(hStmt), SQLITE_DONE); + sqlite3_finalize(hStmt); + sqlite3_close(hDB); + } + + ASSERT_TRUE(proj_is_download_needed(ctx, "dvr90.gtx", false)); + + // Redo download with a progress callback this time. + unlink("proj_test_tmp/dvr90.tif"); + + const auto cbk = [](PJ_CONTEXT *l_ctx, double pct, void *user_data) -> int { + auto vect = static_cast<std::vector<std::pair<PJ_CONTEXT *, double>> *>( + user_data); + vect->push_back(std::pair<PJ_CONTEXT *, double>(l_ctx, pct)); + return true; + }; + + std::vector<std::pair<PJ_CONTEXT *, double>> vectPct; + ASSERT_TRUE(proj_download_file(ctx, "dvr90.gtx", false, cbk, &vectPct)); + ASSERT_EQ(vectPct.size(), 3U); + ASSERT_EQ(vectPct.back().first, ctx); + ASSERT_EQ(vectPct.back().second, 1.0); + + proj_context_destroy(ctx); + putenv(const_cast<char *>("PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES")); + putenv(const_cast<char *>("PROJ_USER_WRITABLE_DIRECTORY=")); + putenv(const_cast<char *>("PROJ_FULL_FILE_CHUNK_SIZE=")); + unlink("proj_test_tmp/cache.db"); + unlink("proj_test_tmp/dvr90.tif"); + rmdir("proj_test_tmp"); +} + +// --------------------------------------------------------------------------- + +TEST(networking, file_api) { + if (!networkAccessOK) { + return; + } + + proj_cleanup(); + unlink("proj_test_tmp/cache.db"); + unlink("proj_test_tmp/dvr90.tif"); + rmdir("proj_test_tmp"); + + putenv(const_cast<char *>("PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=")); + putenv(const_cast<char *>("PROJ_USER_WRITABLE_DIRECTORY=./proj_test_tmp")); + putenv(const_cast<char *>("PROJ_FULL_FILE_CHUNK_SIZE=30000")); + auto ctx = proj_context_create(); + proj_context_set_enable_network(ctx, true); + + struct UserData { + bool in_open = false; + bool in_read = false; + bool in_write = false; + bool in_seek = false; + bool in_tell = false; + bool in_close = false; + bool in_exists = false; + bool in_mkdir = false; + bool in_unlink = false; + bool in_rename = false; + }; + + struct PROJ_FILE_API api; + api.version = 1; + api.open_cbk = [](PJ_CONTEXT *, const char *filename, + PROJ_OPEN_ACCESS access, + void *user_data) -> PROJ_FILE_HANDLE * { + static_cast<UserData *>(user_data)->in_open = true; + return reinterpret_cast<PROJ_FILE_HANDLE *>(fopen( + filename, + access == PROJ_OPEN_ACCESS_READ_ONLY + ? "rb" + : access == PROJ_OPEN_ACCESS_READ_UPDATE ? "r+b" : "w+b")); + }; + api.read_cbk = [](PJ_CONTEXT *, PROJ_FILE_HANDLE *handle, void *buffer, + size_t sizeBytes, void *user_data) -> size_t { + static_cast<UserData *>(user_data)->in_read = true; + return fread(buffer, 1, sizeBytes, reinterpret_cast<FILE *>(handle)); + }; + api.write_cbk = [](PJ_CONTEXT *, PROJ_FILE_HANDLE *handle, + const void *buffer, size_t sizeBytes, + void *user_data) -> size_t { + static_cast<UserData *>(user_data)->in_write = true; + return fwrite(buffer, 1, sizeBytes, reinterpret_cast<FILE *>(handle)); + }; + api.seek_cbk = [](PJ_CONTEXT *, PROJ_FILE_HANDLE *handle, long long offset, + int whence, void *user_data) -> int { + static_cast<UserData *>(user_data)->in_seek = true; + return fseek(reinterpret_cast<FILE *>(handle), + static_cast<long>(offset), whence) == 0; + }; + api.tell_cbk = [](PJ_CONTEXT *, PROJ_FILE_HANDLE *handle, + void *user_data) -> unsigned long long { + static_cast<UserData *>(user_data)->in_tell = true; + return ftell(reinterpret_cast<FILE *>(handle)); + }; + api.close_cbk = [](PJ_CONTEXT *, PROJ_FILE_HANDLE *handle, + void *user_data) -> void { + static_cast<UserData *>(user_data)->in_close = true; + fclose(reinterpret_cast<FILE *>(handle)); + }; + api.exists_cbk = [](PJ_CONTEXT *, const char *filename, + void *user_data) -> int { + static_cast<UserData *>(user_data)->in_exists = true; + struct stat buf; + return stat(filename, &buf) == 0; + }; + api.mkdir_cbk = [](PJ_CONTEXT *, const char *filename, + void *user_data) -> int { + static_cast<UserData *>(user_data)->in_mkdir = true; +#ifdef _WIN32 + return mkdir(filename) == 0; +#else + return mkdir(filename, 0755) == 0; +#endif + }; + api.unlink_cbk = [](PJ_CONTEXT *, const char *filename, + void *user_data) -> int { + static_cast<UserData *>(user_data)->in_unlink = true; + return unlink(filename) == 0; + }; + api.rename_cbk = [](PJ_CONTEXT *, const char *oldPath, const char *newPath, + void *user_data) -> int { + static_cast<UserData *>(user_data)->in_rename = true; + return rename(oldPath, newPath) == 0; + }; + + UserData userData; + ASSERT_TRUE(proj_context_set_fileapi(ctx, &api, &userData)); + + ASSERT_TRUE(proj_is_download_needed(ctx, "dvr90.gtx", false)); + + ASSERT_TRUE(proj_download_file(ctx, "dvr90.gtx", false, nullptr, nullptr)); + + ASSERT_TRUE(userData.in_open); + ASSERT_FALSE(userData.in_read); + ASSERT_TRUE(userData.in_write); + ASSERT_TRUE(userData.in_close); + ASSERT_TRUE(userData.in_exists); + ASSERT_TRUE(userData.in_mkdir); + ASSERT_TRUE(userData.in_unlink); + ASSERT_TRUE(userData.in_rename); + + proj_context_set_enable_network(ctx, false); + + const char *pipeline = + "+proj=pipeline " + "+step +proj=unitconvert +xy_in=deg +xy_out=rad " + "+step +proj=vgridshift +grids=dvr90.gtx +multiplier=1 " + "+step +proj=unitconvert +xy_in=rad +xy_out=deg"; + + auto P = proj_create(ctx, pipeline); + ASSERT_NE(P, nullptr); + + double lon = 12; + double lat = 56; + double z = 0; + proj_trans_generic(P, PJ_FWD, &lon, sizeof(double), 1, &lat, sizeof(double), + 1, &z, sizeof(double), 1, nullptr, 0, 0); + EXPECT_NEAR(z, 36.5909996032715, 1e-10); + + proj_destroy(P); + + ASSERT_TRUE(userData.in_read); + ASSERT_TRUE(userData.in_seek); + + proj_context_destroy(ctx); + putenv(const_cast<char *>("PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY=YES")); + putenv(const_cast<char *>("PROJ_USER_WRITABLE_DIRECTORY=")); + putenv(const_cast<char *>("PROJ_FULL_FILE_CHUNK_SIZE=")); + unlink("proj_test_tmp/cache.db"); + unlink("proj_test_tmp/dvr90.tif"); + rmdir("proj_test_tmp"); +} + +#endif + +} // namespace diff --git a/test/unit/test_operation.cpp b/test/unit/test_operation.cpp index b2c13b6c..8ee5814b 100644 --- a/test/unit/test_operation.cpp +++ b/test/unit/test_operation.cpp @@ -4832,7 +4832,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); } @@ -6508,7 +6508,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"); @@ -6820,7 +6820,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( @@ -8262,8 +8262,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 @@ -8271,8 +8271,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 @@ -8283,8 +8283,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)); } } |
