aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/4D_api.cpp105
-rw-r--r--src/Makefile.am25
-rw-r--r--src/apply_gridshift.cpp366
-rw-r--r--src/apply_vgridshift.cpp361
-rw-r--r--src/apps/gie.cpp1
-rw-r--r--src/apps/projinfo.cpp30
-rw-r--r--src/ctx.cpp36
-rw-r--r--src/datum_set.cpp21
-rw-r--r--src/fileapi.cpp36
-rw-r--r--src/filemanager.cpp1733
-rw-r--r--src/filemanager.hpp100
-rw-r--r--src/gc_reader.cpp248
-rw-r--r--src/gridcatalog.cpp306
-rw-r--r--src/gridinfo.cpp988
-rw-r--r--src/gridlist.cpp220
-rw-r--r--src/grids.cpp3402
-rw-r--r--src/grids.hpp264
-rw-r--r--src/init.cpp6
-rw-r--r--src/iso19111/c_api.cpp20
-rw-r--r--src/iso19111/coordinateoperation.cpp131
-rw-r--r--src/iso19111/factory.cpp213
-rw-r--r--src/lib_proj.cmake34
-rw-r--r--src/malloc.cpp11
-rw-r--r--src/nad_cvt.cpp98
-rw-r--r--src/nad_init.cpp308
-rw-r--r--src/nad_intr.cpp67
-rw-r--r--src/networkfilemanager.cpp2544
-rw-r--r--src/open_lib.cpp359
-rw-r--r--src/pipeline.cpp22
-rw-r--r--src/pj_list.h1
-rw-r--r--src/proj.h166
-rw-r--r--src/proj_internal.h195
-rw-r--r--src/sqlite3_utils.cpp194
-rw-r--r--src/sqlite3_utils.hpp127
-rw-r--r--src/strerrno.cpp1
-rw-r--r--src/transform.cpp145
-rw-r--r--src/transformations/deformation.cpp193
-rw-r--r--src/transformations/hgridshift.cpp89
-rw-r--r--src/transformations/vgridshift.cpp119
-rw-r--r--src/transformations/xyzgridshift.cpp303
40 files changed, 9733 insertions, 3855 deletions
diff --git a/src/4D_api.cpp b/src/4D_api.cpp
index 087cac5c..dabd44a0 100644
--- a/src/4D_api.cpp
+++ b/src/4D_api.cpp
@@ -47,6 +47,8 @@
#include "proj_internal.h"
#include <math.h>
#include "geodesic.h"
+#include "grids.hpp"
+#include "filemanager.hpp"
#include "proj/common.hpp"
#include "proj/coordinateoperation.hpp"
@@ -262,6 +264,9 @@ similarly, but prefers the 2D resp. 3D interfaces if available.
}
PJ_COORD res = direction == PJ_FWD ?
pj_fwd4d( coord, alt.pj ) : pj_inv4d( coord, alt.pj );
+ if( proj_errno(alt.pj) == PJD_ERR_NETWORK_ERROR ) {
+ return proj_coord_error ();
+ }
if( res.xyzt.x != HUGE_VAL ) {
return res;
}
@@ -290,7 +295,7 @@ similarly, but prefers the 2D resp. 3D interfaces if available.
auto coordOperation = dynamic_cast<
NS_PROJ::operation::CoordinateOperation*>(alt.pj->iso_obj.get());
if( coordOperation ) {
- if( coordOperation->gridsNeeded(dbContext).empty() ) {
+ if( coordOperation->gridsNeeded(dbContext, true).empty() ) {
if( P->iCurCoordOp != i ) {
if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_DEBUG) {
std::string msg("Using coordinate operation ");
@@ -1146,7 +1151,10 @@ PJ *proj_create_crs_to_crs_from_pj (PJ_CONTEXT *ctx, const PJ *source_crs, cons
proj_operation_factory_context_set_spatial_criterion(
ctx, operation_ctx, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION);
proj_operation_factory_context_set_grid_availability_use(
- ctx, operation_ctx, PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID);
+ ctx, operation_ctx,
+ pj_context_is_network_enabled(ctx) ?
+ PROJ_GRID_AVAILABILITY_KNOWN_AVAILABLE:
+ PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID);
auto op_list = proj_create_operations(ctx, source_crs, target_crs, operation_ctx);
@@ -1476,10 +1484,20 @@ PJ_INFO proj_info (void) {
/* build search path string */
auto ctx = pj_get_default_ctx();
if (!ctx || ctx->search_paths.empty()) {
- const char *envPROJ_LIB = getenv("PROJ_LIB");
- buf = path_append(buf, envPROJ_LIB, &buf_size);
+ // Env var mostly for testing purposes and being independent from
+ // an existing installation
+ const char* ignoreUserWritableDirectory =
+ getenv("PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY");
+ if( ignoreUserWritableDirectory == nullptr ||
+ ignoreUserWritableDirectory[0] == '\0' ) {
+ buf = path_append(buf,
+ pj_context_get_user_writable_directory(ctx, false).c_str(),
+ &buf_size);
+ }
+ const std::string envPROJ_LIB = NS_PROJ::FileManager::getProjLibEnvVar(ctx);
+ buf = path_append(buf, envPROJ_LIB.empty() ? nullptr : envPROJ_LIB.c_str(), &buf_size);
#ifdef PROJ_LIB
- if (envPROJ_LIB == nullptr) {
+ if (envPROJ_LIB.empty()) {
buf = path_append(buf, PROJ_LIB, &buf_size);
}
#endif
@@ -1583,43 +1601,65 @@ PJ_GRID_INFO proj_grid_info(const char *gridname) {
/*PJ_CONTEXT *ctx = proj_context_create(); */
PJ_CONTEXT *ctx = pj_get_default_ctx();
- PJ_GRIDINFO *gridinfo = pj_gridinfo_init(ctx, gridname);
memset(&grinfo, 0, sizeof(PJ_GRID_INFO));
- /* in case the grid wasn't found */
- if (gridinfo->filename == nullptr || gridinfo->ct == nullptr) {
- pj_gridinfo_free(ctx, gridinfo);
- strcpy(grinfo.format, "missing");
- return grinfo;
- }
-
- /* The string copies below are automatically null-terminated due to */
- /* the memset above, so strncpy is safe */
+ const auto fillGridInfo = [&grinfo, ctx, gridname]
+ (const NS_PROJ::Grid& grid, const std::string& format)
+ {
+ const auto& extent = grid.extentAndRes();
- /* name of grid */
- strncpy (grinfo.gridname, gridname, sizeof(grinfo.gridname) - 1);
+ /* name of grid */
+ strncpy (grinfo.gridname, gridname, sizeof(grinfo.gridname) - 1);
- /* full path of grid */
- pj_find_file(ctx, gridname, grinfo.filename, sizeof(grinfo.filename) - 1);
+ /* full path of grid */
+ pj_find_file(ctx, gridname, grinfo.filename, sizeof(grinfo.filename) - 1);
- /* grid format */
- strncpy (grinfo.format, gridinfo->format, sizeof(grinfo.format) - 1);
+ /* grid format */
+ strncpy (grinfo.format, format.c_str(), sizeof(grinfo.format) - 1);
- /* grid size */
- grinfo.n_lon = gridinfo->ct->lim.lam;
- grinfo.n_lat = gridinfo->ct->lim.phi;
+ /* grid size */
+ grinfo.n_lon = grid.width();
+ grinfo.n_lat = grid.height();
- /* cell size */
- grinfo.cs_lon = gridinfo->ct->del.lam;
- grinfo.cs_lat = gridinfo->ct->del.phi;
+ /* cell size */
+ grinfo.cs_lon = extent.resLon;
+ grinfo.cs_lat = extent.resLat;
- /* bounds of grid */
- grinfo.lowerleft = gridinfo->ct->ll;
- grinfo.upperright.lam = grinfo.lowerleft.lam + grinfo.n_lon*grinfo.cs_lon;
- grinfo.upperright.phi = grinfo.lowerleft.phi + grinfo.n_lat*grinfo.cs_lat;
+ /* bounds of grid */
+ grinfo.lowerleft.lam = extent.westLon;
+ grinfo.lowerleft.phi = extent.southLat;
+ grinfo.upperright.lam = extent.eastLon;
+ grinfo.upperright.phi = extent.northLat;
+ };
- pj_gridinfo_free(ctx, gridinfo);
+ {
+ const auto gridSet = NS_PROJ::VerticalShiftGridSet::open(ctx, gridname);
+ if( gridSet )
+ {
+ const auto& grids = gridSet->grids();
+ if( !grids.empty() )
+ {
+ const auto& grid = grids.front();
+ fillGridInfo(*grid, gridSet->format());
+ return grinfo;
+ }
+ }
+ }
+ {
+ const auto gridSet = NS_PROJ::HorizontalShiftGridSet::open(ctx, gridname);
+ if( gridSet )
+ {
+ const auto& grids = gridSet->grids();
+ if( !grids.empty() )
+ {
+ const auto& grid = grids.front();
+ fillGridInfo(*grid, gridSet->format());
+ return grinfo;
+ }
+ }
+ }
+ strcpy(grinfo.format, "missing");
return grinfo;
}
@@ -1763,3 +1803,4 @@ PJ_FACTORS proj_factors(PJ *P, PJ_COORD lp) {
return factors;
}
+
diff --git a/src/Makefile.am b/src/Makefile.am
index 62821ff9..8ab30a33 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -7,7 +7,7 @@ TESTS = geodtest
check_PROGRAMS = geodtest
AM_CPPFLAGS = -DPROJ_LIB=\"$(pkgdatadir)\" \
- -DMUTEX_@MUTEX_SETTING@ -I$(top_srcdir)/include @SQLITE3_CFLAGS@
+ -DMUTEX_@MUTEX_SETTING@ -I$(top_srcdir)/include @SQLITE3_CFLAGS@ @TIFF_CFLAGS@ @TIFF_ENABLED_FLAGS@ @CURL_CFLAGS@ @CURL_ENABLED_FLAGS@
AM_CXXFLAGS = @CXX_WFLAGS@ @FLTO_FLAG@
include_HEADERS = proj.h proj_experimental.h proj_constants.h proj_api.h geodesic.h \
@@ -43,7 +43,7 @@ geodtest_LDADD = libproj.la
lib_LTLIBRARIES = libproj.la
libproj_la_LDFLAGS = -no-undefined -version-info 18:0:3
-libproj_la_LIBADD = @SQLITE3_LIBS@
+libproj_la_LIBADD = @SQLITE3_LIBS@ @TIFF_LIBS@ @CURL_LIBS@
libproj_la_SOURCES = \
pj_list.h proj_internal.h \
@@ -183,23 +183,22 @@ libproj_la_SOURCES = \
transformations/horner.cpp \
transformations/molodensky.cpp \
transformations/vgridshift.cpp \
+ transformations/xyzgridshift.cpp \
\
aasincos.cpp adjlon.cpp \
dmstor.cpp auth.cpp \
deriv.cpp ell_set.cpp ellps.cpp errno.cpp \
factors.cpp fwd.cpp init.cpp inv.cpp \
list.cpp malloc.cpp mlfn.cpp msfn.cpp proj_mdist.cpp \
- open_lib.cpp param.cpp phi2.cpp pr_list.cpp \
+ param.cpp phi2.cpp pr_list.cpp \
qsfn.cpp strerrno.cpp \
tsfn.cpp units.cpp ctx.cpp log.cpp zpoly1.cpp rtodms.cpp \
release.cpp gauss.cpp \
fileapi.cpp \
\
- gc_reader.cpp gridcatalog.cpp \
- nad_cvt.cpp nad_init.cpp nad_intr.cpp \
- apply_gridshift.cpp datums.cpp datum_set.cpp transform.cpp \
- geocent.cpp geocent.h utils.cpp gridinfo.cpp gridlist.cpp \
- mutex.cpp initcache.cpp apply_vgridshift.cpp geodesic.c \
+ datums.cpp datum_set.cpp transform.cpp \
+ geocent.cpp geocent.h utils.cpp \
+ mutex.cpp initcache.cpp geodesic.c \
strtod.cpp \
\
4D_api.cpp pipeline.cpp \
@@ -213,7 +212,15 @@ libproj_la_SOURCES = \
proj_json_streaming_writer.hpp \
proj_json_streaming_writer.cpp \
\
- tracing.cpp
+ tracing.cpp \
+ \
+ grids.hpp \
+ grids.cpp \
+ filemanager.hpp \
+ filemanager.cpp \
+ networkfilemanager.cpp \
+ sqlite3_utils.hpp \
+ sqlite3_utils.cpp
# The sed hack is to please MSVC
diff --git a/src/apply_gridshift.cpp b/src/apply_gridshift.cpp
deleted file mode 100644
index 39e7c3b5..00000000
--- a/src/apply_gridshift.cpp
+++ /dev/null
@@ -1,366 +0,0 @@
-/******************************************************************************
- * Project: PROJ.4
- * Purpose: Apply datum shifts based on grid shift files (normally NAD27 to
- * NAD83 or the reverse). This module is responsible for keeping
- * a list of loaded grids, and calling with each one that is
- * allowed for a given datum (expressed as the nadgrids= parameter).
- * Author: Frank Warmerdam, warmerdam@pobox.com
- *
- ******************************************************************************
- * Copyright (c) 2000, Frank Warmerdam <warmerdam@pobox.com>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included
- * in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- *****************************************************************************/
-
-#define PJ_LIB__
-
-#include <math.h>
-#include <stdio.h>
-#include <string.h>
-
-#include "proj.h"
-#include "proj_internal.h"
-
-/************************************************************************/
-/* pj_apply_gridshift() */
-/* */
-/* This is the externally callable interface - part of the */
-/* public API - though it is not used internally any more and I */
-/* doubt it is used by any other applications. But we preserve */
-/* it to honour our public api. */
-/************************************************************************/
-
-int pj_apply_gridshift( projCtx ctx, const char *nadgrids, int inverse,
- long point_count, int point_offset,
- double *x, double *y, double *z )
-
-{
- PJ_GRIDINFO **gridlist;
- int grid_count;
- int ret;
-
- gridlist = pj_gridlist_from_nadgrids( ctx, nadgrids, &grid_count );
-
- if( gridlist == nullptr || grid_count == 0 )
- {
- pj_dalloc( gridlist );
- return ctx->last_errno;
- }
-
- ret = pj_apply_gridshift_3( ctx, gridlist, grid_count, inverse,
- point_count, point_offset, x, y, z );
-
- /*
- ** Note this frees the array of grid list pointers, but not the grids
- ** which is as intended. The grids themselves live on.
- */
- pj_dalloc( gridlist );
-
- return ret;
-}
-
-/************************************************************************/
-/* pj_apply_gridshift_2() */
-/* */
-/* This implementation uses the gridlist from a coordinate */
-/* system definition. If the gridlist has not yet been */
-/* populated in the coordinate system definition we set it up */
-/* now. */
-/************************************************************************/
-
-int pj_apply_gridshift_2( PJ *defn, int inverse,
- long point_count, int point_offset,
- double *x, double *y, double *z )
-
-{
- if( defn->catalog_name != nullptr )
- return pj_gc_apply_gridshift( defn, inverse, point_count, point_offset,
- x, y, z );
-
- if( defn->gridlist == nullptr )
- {
- defn->gridlist =
- pj_gridlist_from_nadgrids( pj_get_ctx( defn ),
- pj_param(defn->ctx, defn->params,"snadgrids").s,
- &(defn->gridlist_count) );
-
- if( defn->gridlist == nullptr || defn->gridlist_count == 0 )
- return defn->ctx->last_errno;
- }
-
- return pj_apply_gridshift_3( pj_get_ctx( defn ),
- defn->gridlist, defn->gridlist_count, inverse,
- point_count, point_offset, x, y, z );
-}
-
-/************************************************************************/
-/* find_ctable() */
-/* */
-/* Determine which grid is the correct given an input coordinate. */
-/************************************************************************/
-
-struct CTABLE* find_ctable(projCtx ctx, PJ_LP input, int grid_count, PJ_GRIDINFO **tables) {
- int itable;
-
- /* keep trying till we find a table that works */
- for( itable = 0; itable < grid_count; itable++ )
- {
-
- PJ_GRIDINFO *gi = tables[itable];
- struct CTABLE *ct = gi->ct;
- double epsilon = (fabs(ct->del.phi)+fabs(ct->del.lam))/10000.0;
- /* skip tables that don't match our point at all. */
- if ( ct->ll.phi - epsilon > input.phi
- || ct->ll.lam - epsilon > input.lam
- || (ct->ll.phi + (ct->lim.phi-1) * ct->del.phi + epsilon < input.phi)
- || (ct->ll.lam + (ct->lim.lam-1) * ct->del.lam + epsilon < input.lam) ) {
- continue;
- }
-
- /* If we have child nodes, check to see if any of them apply. */
- while( gi->child )
- {
- PJ_GRIDINFO *child;
-
- for( child = gi->child; child != nullptr; child = child->next )
- {
- struct CTABLE *ct1 = child->ct;
- epsilon = (fabs(ct1->del.phi)+fabs(ct1->del.lam))/10000.0;
-
- if( ct1->ll.phi - epsilon > input.phi
- || ct1->ll.lam - epsilon > input.lam
- || (ct1->ll.phi+(ct1->lim.phi-1)*ct1->del.phi + epsilon < input.phi)
- || (ct1->ll.lam+(ct1->lim.lam-1)*ct1->del.lam + epsilon < input.lam) ) {
- continue;
- }
- break;
- }
-
- /* If we didn't find a child then nothing more to do */
- if( child == nullptr ) break;
-
- /* Otherwise use the child, first checking it's children */
- gi = child;
- ct = child->ct;
- }
- /* load the grid shift info if we don't have it. */
- if( ct->cvs == nullptr) {
- if (!pj_gridinfo_load( ctx, gi ) ) {
- pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- return nullptr;
- }
- }
- /* if we get this far we have found a suitable grid */
- return ct;
- }
-
- return nullptr;
-}
-
-/************************************************************************/
-/* pj_apply_gridshift_3() */
-/* */
-/* This is the real workhorse, given a gridlist. */
-/************************************************************************/
-
-int pj_apply_gridshift_3( projCtx ctx, PJ_GRIDINFO **gridlist, int gridlist_count,
- int inverse, long point_count, int point_offset,
- double *x, double *y, double *z )
-{
- int i;
- struct CTABLE *ct;
- static int debug_count = 0;
- (void) z;
-
- if( gridlist== nullptr || gridlist_count == 0 )
- {
- pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
- return PJD_ERR_FAILED_TO_LOAD_GRID;
- }
-
- ctx->last_errno = 0;
-
- for( i = 0; i < point_count; i++ )
- {
- long io = i * point_offset;
- PJ_LP input, output;
- int itable;
-
- input.phi = y[io];
- input.lam = x[io];
- output.phi = HUGE_VAL;
- output.lam = HUGE_VAL;
-
- ct = find_ctable(ctx, input, gridlist_count, gridlist);
- if( ct != nullptr )
- {
- output = nad_cvt( ctx, input, inverse, ct, gridlist_count, gridlist);
-
- if ( output.lam != HUGE_VAL && debug_count++ < 20 )
- pj_log( ctx, PJ_LOG_DEBUG_MINOR, "pj_apply_gridshift(): used %s", ct->id );
- }
-
- if ( output.lam == HUGE_VAL )
- {
- if( ctx->debug_level >= PJ_LOG_DEBUG_MAJOR )
- {
- pj_log( ctx, PJ_LOG_DEBUG_MAJOR,
- "pj_apply_gridshift(): failed to find a grid shift table for\n"
- " location (%.7fdW,%.7fdN)",
- x[io] * RAD_TO_DEG,
- y[io] * RAD_TO_DEG );
- for( itable = 0; itable < gridlist_count; itable++ )
- {
- PJ_GRIDINFO *gi = gridlist[itable];
- if( itable == 0 )
- pj_log( ctx, PJ_LOG_DEBUG_MAJOR, " tried: %s", gi->gridname );
- else
- pj_log( ctx, PJ_LOG_DEBUG_MAJOR, ",%s", gi->gridname );
- }
- }
-
- /*
- * We don't actually have any machinery currently to set the
- * following macro, so this is mostly kept here to make it clear
- * how we ought to operate if we wanted to make it super clear
- * that an error has occurred when points are outside our available
- * datum shift areas. But if this is on, we will find that "low
- * value" points on the fringes of some datasets will completely
- * fail causing lots of problems when it is more or less ok to
- * just not apply a datum shift. So rather than deal with
- * that we just fallback to no shift. (see also bug #45).
- */
-#ifdef ERR_GRID_AREA_TRANSIENT_SEVERE
- y[io] = HUGE_VAL;
- x[io] = HUGE_VAL;
-#else
- /* leave x/y unshifted. */
-#endif
- }
- else
- {
- y[io] = output.phi;
- x[io] = output.lam;
- }
- }
-
- return 0;
-}
-
-/**********************************************/
-int proj_hgrid_init(PJ* P, const char *grids) {
-/**********************************************
-
- Initizalize and populate list of horizontal
- grids.
-
- Takes a PJ-object and the plus-parameter
- name that is used in the proj-string to
- specify the grids to load, e.g. "+grids".
- The + should be left out here.
-
- Returns the number of loaded grids.
-
-***********************************************/
-
- /* prepend "s" to the "grids" string to allow usage with pj_param */
- char *sgrids = (char *) pj_malloc( (strlen(grids)+1+1) *sizeof(char) );
- sprintf(sgrids, "%s%s", "s", grids);
-
- if (P->gridlist == nullptr) {
- P->gridlist = pj_gridlist_from_nadgrids(
- P->ctx,
- pj_param(P->ctx, P->params, sgrids).s,
- &(P->gridlist_count)
- );
-
- if( P->gridlist == nullptr || P->gridlist_count == 0 ) {
- pj_dealloc(sgrids);
- return 0;
- }
- }
-
- if (P->gridlist_count == 0) {
- proj_errno_set(P, PJD_ERR_FAILED_TO_LOAD_GRID);
- }
-
- pj_dealloc(sgrids);
- return P->gridlist_count;
-}
-
-/********************************************/
-/* proj_hgrid_value() */
-/* */
-/* Return coordinate offset in grid */
-/********************************************/
-PJ_LP proj_hgrid_value(PJ *P, PJ_LP lp) {
- struct CTABLE *ct;
- PJ_LP out = proj_coord_error().lp;
-
- ct = find_ctable(P->ctx, lp, P->gridlist_count, P->gridlist);
- if (ct == nullptr) {
- pj_ctx_set_errno( P->ctx, PJD_ERR_GRID_AREA);
- return out;
- }
-
- /* normalize input to ll origin */
- lp.lam -= ct->ll.lam;
- lp.phi -= ct->ll.phi;
-
- lp.lam = adjlon(lp.lam - M_PI) + M_PI;
-
- out = nad_intr(lp, ct);
-
- if (out.lam == HUGE_VAL || out.phi == HUGE_VAL) {
- pj_ctx_set_errno(P->ctx, PJD_ERR_GRID_AREA);
- }
-
- return out;
-}
-
-PJ_LP proj_hgrid_apply(PJ *P, PJ_LP lp, PJ_DIRECTION direction) {
- struct CTABLE *ct;
- int inverse;
- PJ_LP out;
-
- out.lam = HUGE_VAL; out.phi = HUGE_VAL;
-
- ct = find_ctable(P->ctx, lp, P->gridlist_count, P->gridlist);
-
- if (ct == nullptr || ct->cvs == nullptr) {
- if( P->gridlist_count == 1 &&
- strcmp(P->gridlist[0]->gridname, "null") == 0) {
- // TODO: remove this particular case that is put there just to be
- // able to handle longitudes outside of -180,180
- out = lp;
- } else {
- pj_ctx_set_errno( P->ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- }
- return out;
- }
-
- inverse = direction == PJ_FWD ? 0 : 1;
- out = nad_cvt(P->ctx, lp, inverse, ct, P->gridlist_count, P->gridlist);
-
- if (out.lam == HUGE_VAL || out.phi == HUGE_VAL)
- pj_ctx_set_errno(P->ctx, PJD_ERR_GRID_AREA);
-
- return out;
-
-}
diff --git a/src/apply_vgridshift.cpp b/src/apply_vgridshift.cpp
deleted file mode 100644
index daa44858..00000000
--- a/src/apply_vgridshift.cpp
+++ /dev/null
@@ -1,361 +0,0 @@
-/******************************************************************************
- * Project: PROJ.4
- * Purpose: Apply vertical datum shifts based on grid shift files, normally
- * geoid grids mapping WGS84 to NAVD88 or something similar.
- * Author: Frank Warmerdam, warmerdam@pobox.com
- *
- ******************************************************************************
- * Copyright (c) 2010, Frank Warmerdam <warmerdam@pobox.com>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included
- * in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- *****************************************************************************/
-
-#define PJ_LIB__
-
-#include <assert.h>
-#include <stdio.h>
-#include <string.h>
-
-#include <math.h>
-#include "proj_internal.h"
-
-static int is_nodata(float value, double vmultiplier)
-{
- /* nodata? */
- /* GTX official nodata value if -88.88880f, but some grids also */
- /* use other big values for nodata (e.g naptrans2008.gtx has */
- /* nodata values like -2147479936), so test them too */
- return value * vmultiplier > 1000 || value * vmultiplier < -1000 || value == -88.88880f;
-}
-
-static double read_vgrid_value( PJ *defn, PJ_LP input, double vmultiplier, int *gridlist_count_p, PJ_GRIDINFO **tables, struct CTABLE *ct) {
- int itable = 0;
- double value = HUGE_VAL;
- double grid_x, grid_y;
- long grid_ix, grid_iy;
- long grid_ix2, grid_iy2;
- float *cvs;
- /* do not deal with NaN coordinates */
- /* cppcheck-suppress duplicateExpression */
- if( isnan(input.phi) || isnan(input.lam) )
- itable = *gridlist_count_p;
-
- /* keep trying till we find a table that works */
- for ( ; itable < *gridlist_count_p; itable++ )
- {
- PJ_GRIDINFO *gi = tables[itable];
-
- ct = gi->ct;
-
- /* skip tables that don't match our point at all (latitude check). */
- if( ct->ll.phi > input.phi
- || ct->ll.phi + (ct->lim.phi-1) * ct->del.phi < input.phi )
- continue;
-
- bool fullWorldLongExtent = false;
- if( fabs(ct->lim.lam * ct->del.lam - 2 * M_PI) < 1e-10 )
- {
- fullWorldLongExtent = true;
- }
-
- /* skip tables that don't match our point at all (longitude check). */
- else if( ct->ll.lam > input.lam
- || ct->ll.lam + (ct->lim.lam-1) * ct->del.lam < input.lam )
- continue;
-
- /* If we have child nodes, check to see if any of them apply. */
- while( gi->child != nullptr )
- {
- PJ_GRIDINFO *child;
-
- for( child = gi->child; child != nullptr; child = child->next )
- {
- struct CTABLE *ct1 = child->ct;
-
- fullWorldLongExtent = false;
-
- if( ct1->ll.phi > input.phi
- || ct1->ll.phi+(ct1->lim.phi-1)*ct1->del.phi < input.phi)
- continue;
-
- if( fabs(ct1->lim.lam * ct1->del.lam - 2 * M_PI) < 1e-10 )
- {
- fullWorldLongExtent = true;
- }
- else if( ct1->ll.lam > input.lam
- || ct1->ll.lam+(ct1->lim.lam-1)*ct1->del.lam < input.lam)
- continue;
-
- break;
- }
-
- /* we didn't find a more refined child node to use, so go with current grid */
- if( child == nullptr )
- {
- break;
- }
-
- /* Otherwise let's try for childrens children .. */
- gi = child;
- ct = child->ct;
- }
-
- /* load the grid shift info if we don't have it. */
- if( ct->cvs == nullptr )
- {
- pj_gridinfo_load( pj_get_ctx(defn), gi );
- }
- if( ct->cvs == nullptr )
- {
- pj_ctx_set_errno( defn->ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- return PJD_ERR_FAILED_TO_LOAD_GRID;
- }
-
- /* Interpolation a location within the grid */
- grid_x = (input.lam - ct->ll.lam) / ct->del.lam;
- if( fullWorldLongExtent ) {
- // The first fmod goes to ]-lim, lim[ range
- // So we add lim again to be in ]0, 2*lim[ and fmod again
- grid_x = fmod(
- fmod(grid_x + ct->lim.lam, ct->lim.lam) + ct->lim.lam,
- ct->lim.lam);
- }
- grid_y = (input.phi - ct->ll.phi) / ct->del.phi;
- grid_ix = lround(floor(grid_x));
- assert(grid_ix >= 0 && grid_ix < ct->lim.lam);
- grid_iy = lround(floor(grid_y));
- assert(grid_iy >= 0 && grid_iy < ct->lim.phi);
- grid_x -= grid_ix;
- grid_y -= grid_iy;
-
- grid_ix2 = grid_ix + 1;
- if( grid_ix2 >= ct->lim.lam ) {
- if( fullWorldLongExtent ) {
- grid_ix2 = 0;
- } else {
- grid_ix2 = ct->lim.lam - 1;
- }
- }
- grid_iy2 = grid_iy + 1;
- if( grid_iy2 >= ct->lim.phi )
- grid_iy2 = ct->lim.phi - 1;
-
- cvs = (float *) ct->cvs;
- {
- float value_a = cvs[grid_ix + grid_iy * ct->lim.lam];
- float value_b = cvs[grid_ix2 + grid_iy * ct->lim.lam];
- float value_c = cvs[grid_ix + grid_iy2 * ct->lim.lam];
- float value_d = cvs[grid_ix2 + grid_iy2 * ct->lim.lam];
- double total_weight = 0.0;
- int n_weights = 0;
- value = 0.0f;
- if( !is_nodata(value_a, vmultiplier) )
- {
- double weight = (1.0-grid_x) * (1.0-grid_y);
- value += value_a * weight;
- total_weight += weight;
- n_weights ++;
- }
- if( !is_nodata(value_b, vmultiplier) )
- {
- double weight = (grid_x) * (1.0-grid_y);
- value += value_b * weight;
- total_weight += weight;
- n_weights ++;
- }
- if( !is_nodata(value_c, vmultiplier) )
- {
- double weight = (1.0-grid_x) * (grid_y);
- value += value_c * weight;
- total_weight += weight;
- n_weights ++;
- }
- if( !is_nodata(value_d, vmultiplier) )
- {
- double weight = (grid_x) * (grid_y);
- value += value_d * weight;
- total_weight += weight;
- n_weights ++;
- }
- if( n_weights == 0 )
- value = HUGE_VAL;
- else if( n_weights != 4 )
- value /= total_weight;
- }
-
- }
-
- return value * vmultiplier;
-}
-
-/************************************************************************/
-/* pj_apply_vgridshift() */
-/* */
-/* This implementation takes uses the gridlist from a coordinate */
-/* system definition. If the gridlist has not yet been */
-/* populated in the coordinate system definition we set it up */
-/* now. */
-/************************************************************************/
-int pj_apply_vgridshift( PJ *defn, const char *listname,
- PJ_GRIDINFO ***gridlist_p,
- int *gridlist_count_p,
- int inverse,
- long point_count, int point_offset,
- double *x, double *y, double *z )
-
-{
- int i;
- static int debug_count = 0;
- PJ_GRIDINFO **tables;
- struct CTABLE ct;
-
- if( *gridlist_p == nullptr )
- {
- *gridlist_p =
- pj_gridlist_from_nadgrids( pj_get_ctx(defn),
- pj_param(defn->ctx,defn->params,listname).s,
- gridlist_count_p );
-
- if( *gridlist_p == nullptr || *gridlist_count_p == 0 )
- return defn->ctx->last_errno;
- }
-
- if( *gridlist_count_p == 0 )
- {
- pj_ctx_set_errno( defn->ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
- return PJD_ERR_FAILED_TO_LOAD_GRID;
- }
-
- tables = *gridlist_p;
- defn->ctx->last_errno = 0;
-
- for( i = 0; i < point_count; i++ )
- {
- double value;
- long io = i * point_offset;
- PJ_LP input;
-
- input.phi = y[io];
- input.lam = x[io];
-
- value = read_vgrid_value(defn, input, 1.0, gridlist_count_p, tables, &ct);
-
- if( inverse )
- z[io] -= value;
- else
- z[io] += value;
- if( value != HUGE_VAL )
- {
- if( debug_count++ < 20 ) {
- proj_log_trace(defn, "pj_apply_gridshift(): used %s", ct.id);
- break;
- }
- }
-
- if( value == HUGE_VAL )
- {
- int itable;
- std::string gridlist;
-
- proj_log_debug(defn,
- "pj_apply_vgridshift(): failed to find a grid shift table for\n"
- " location (%.7fdW,%.7fdN)",
- x[io] * RAD_TO_DEG,
- y[io] * RAD_TO_DEG );
-
- for( itable = 0; itable < *gridlist_count_p; itable++ )
- {
- PJ_GRIDINFO *gi = tables[itable];
- if( itable == 0 )
- gridlist += " tried: ";
- else
- gridlist += ',';
- gridlist += gi->gridname;
- }
-
- proj_log_debug(defn, "%s", gridlist.c_str());
- pj_ctx_set_errno( defn->ctx, PJD_ERR_GRID_AREA );
-
- return PJD_ERR_GRID_AREA;
- }
- }
-
- return 0;
-}
-
-/**********************************************/
-int proj_vgrid_init(PJ* P, const char *grids) {
-/**********************************************
-
- Initizalize and populate gridlist.
-
- Takes a PJ-object and the plus-parameter
- name that is used in the proj-string to
- specify the grids to load, e.g. "+grids".
- The + should be left out here.
-
- Returns the number of loaded grids.
-
-***********************************************/
-
- /* prepend "s" to the "grids" string to allow usage with pj_param */
- char *sgrids = (char *) pj_malloc( (strlen(grids)+1+1) *sizeof(char) );
- sprintf(sgrids, "%s%s", "s", grids);
-
- if (P->vgridlist_geoid == nullptr) {
- P->vgridlist_geoid = pj_gridlist_from_nadgrids(
- P->ctx,
- pj_param(P->ctx, P->params, sgrids).s,
- &(P->vgridlist_geoid_count)
- );
-
- if( P->vgridlist_geoid == nullptr || P->vgridlist_geoid_count == 0 ) {
- pj_dealloc(sgrids);
- return 0;
- }
- }
-
- if (P->vgridlist_geoid_count == 0) {
- proj_errno_set(P, PJD_ERR_FAILED_TO_LOAD_GRID);
- }
-
- pj_dealloc(sgrids);
- return P->vgridlist_geoid_count;
-}
-
-/***********************************************/
-double proj_vgrid_value(PJ *P, PJ_LP lp, double vmultiplier){
-/***********************************************
-
- Read grid value at position lp in grids loaded
- with proj_grid_init.
-
- Returns the grid value of the given coordinate.
-
-************************************************/
-
- struct CTABLE used_grid;
- double value;
- memset(&used_grid, 0, sizeof(struct CTABLE));
-
- value = read_vgrid_value(P, lp, vmultiplier, &(P->vgridlist_geoid_count), P->vgridlist_geoid, &used_grid);
- proj_log_trace(P, "proj_vgrid_value: (%f, %f) = %f", lp.lam*RAD_TO_DEG, lp.phi*RAD_TO_DEG, value);
-
- return value;
-}
diff --git a/src/apps/gie.cpp b/src/apps/gie.cpp
index 6a67b55d..d9907776 100644
--- a/src/apps/gie.cpp
+++ b/src/apps/gie.cpp
@@ -1153,6 +1153,7 @@ static const struct errno_vs_err_const lookup[] = {
{"pjd_err_inconsistent_unit" , -59},
{"pjd_err_mutually_exclusive_args" , -60},
{"pjd_err_generic_error" , -61},
+ {"pjd_err_network_error" , -62},
{"pjd_err_dont_skip" , 5555},
{"pjd_err_unknown" , 9999},
{"pjd_err_enomem" , ENOMEM},
diff --git a/src/apps/projinfo.cpp b/src/apps/projinfo.cpp
index bef47746..27ea278a 100644
--- a/src/apps/projinfo.cpp
+++ b/src/apps/projinfo.cpp
@@ -87,7 +87,8 @@ static void usage() {
<< " [--spatial-test contains|intersects]" << std::endl
<< " [--crs-extent-use none|both|intersection|smallest]"
<< std::endl
- << " [--grid-check none|discard_missing|sort] "
+ << " [--grid-check "
+ "none|discard_missing|sort|known_available] "
"[--show-superseded]"
<< std::endl
<< " [--pivot-crs always|if_no_direct_transformation|"
@@ -554,7 +555,7 @@ static void outputObject(
auto op = dynamic_cast<CoordinateOperation *>(obj.get());
if (op && dbContext && getenv("PROJINFO_NO_GRID_CHECK") == nullptr) {
try {
- auto setGrids = op->gridsNeeded(dbContext);
+ auto setGrids = op->gridsNeeded(dbContext, false);
bool firstWarning = true;
for (const auto &grid : setGrids) {
if (!grid.available) {
@@ -570,6 +571,7 @@ static void outputObject(
if (!grid.url.empty()) {
std::cout << " at " << grid.url;
}
+ std::cout << ", or on CDN";
} else if (!grid.url.empty()) {
std::cout << " Can be obtained at " << grid.url;
}
@@ -584,8 +586,9 @@ static void outputObject(
// ---------------------------------------------------------------------------
-static void outputOperationSummary(const CoordinateOperationNNPtr &op,
- const DatabaseContextPtr &dbContext) {
+static void outputOperationSummary(
+ const CoordinateOperationNNPtr &op, const DatabaseContextPtr &dbContext,
+ CoordinateOperationContext::GridAvailabilityUse gridAvailabilityUse) {
auto ids = op->identifiers();
if (!ids.empty()) {
std::cout << *(ids[0]->codeSpace()) << ":" << ids[0]->code();
@@ -631,10 +634,16 @@ static void outputOperationSummary(const CoordinateOperationNNPtr &op,
if (dbContext && getenv("PROJINFO_NO_GRID_CHECK") == nullptr) {
try {
- auto setGrids = op->gridsNeeded(dbContext);
+ auto setGrids = op->gridsNeeded(dbContext, false);
for (const auto &grid : setGrids) {
if (!grid.available) {
std::cout << ", at least one grid missing";
+ if (gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ KNOWN_AVAILABLE &&
+ !grid.packageName.empty()) {
+ std::cout << " on the system, but available on CDN";
+ }
break;
}
}
@@ -731,7 +740,7 @@ static void outputOperations(
}
if (summary) {
for (const auto &op : list) {
- outputOperationSummary(op, dbContext);
+ outputOperationSummary(op, dbContext, gridAvailabilityUse);
}
} else {
bool first = true;
@@ -744,7 +753,7 @@ static void outputOperations(
std::cout << "-------------------------------------" << std::endl;
std::cout << "Operation No. " << (i + 1) << ":" << std::endl
<< std::endl;
- outputOperationSummary(op, dbContext);
+ outputOperationSummary(op, dbContext, gridAvailabilityUse);
std::cout << std::endl;
outputObject(dbContext, op, allowUseIntermediateCRS, outputOpt);
}
@@ -777,7 +786,9 @@ int main(int argc, char **argv) {
CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST;
bool buildBoundCRSToWGS84 = false;
CoordinateOperationContext::GridAvailabilityUse gridAvailabilityUse =
- CoordinateOperationContext::GridAvailabilityUse::USE_FOR_SORTING;
+ pj_context_is_network_enabled(nullptr)
+ ? CoordinateOperationContext::GridAvailabilityUse::KNOWN_AVAILABLE
+ : CoordinateOperationContext::GridAvailabilityUse::USE_FOR_SORTING;
CoordinateOperationContext::IntermediateCRSUse allowUseIntermediateCRS =
CoordinateOperationContext::IntermediateCRSUse::
IF_NO_DIRECT_TRANSFORMATION;
@@ -992,6 +1003,9 @@ int main(int argc, char **argv) {
} else if (ci_equal(value, "sort")) {
gridAvailabilityUse = CoordinateOperationContext::
GridAvailabilityUse::USE_FOR_SORTING;
+ } else if (ci_equal(value, "known_available")) {
+ gridAvailabilityUse = CoordinateOperationContext::
+ GridAvailabilityUse::KNOWN_AVAILABLE;
} else {
std::cerr << "Unrecognized value for option --grid-check: "
<< value << std::endl;
diff --git a/src/ctx.cpp b/src/ctx.cpp
index bcb6e1cc..6172b3c8 100644
--- a/src/ctx.cpp
+++ b/src/ctx.cpp
@@ -33,6 +33,7 @@
#include "proj_experimental.h"
#include "proj_internal.h"
+#include "filemanager.hpp"
/************************************************************************/
/* pj_get_ctx() */
@@ -60,9 +61,9 @@ void pj_set_ctx( projPJ pj, projCtx ctx )
if (pj==nullptr)
return;
pj->ctx = ctx;
- if( pj->is_pipeline )
+ if( pj->reassign_context )
{
- pj_pipeline_assign_context_to_steps(pj, ctx);
+ pj->reassign_context(pj, ctx);
}
for( const auto &alt: pj->alternativeCoordinateOperations )
{
@@ -95,7 +96,8 @@ projCtx_t projCtx_t::createDefault()
projCtx_t ctx;
ctx.debug_level = PJ_LOG_NONE;
ctx.logger = pj_stderr_logger;
- ctx.fileapi = pj_get_default_fileapi();
+ ctx.fileapi_legacy = pj_get_default_fileapi();
+ NS_PROJ::FileManager::fillDefaultNetworkInterface(&ctx);
if( getenv("PROJ_DEBUG") != nullptr )
{
@@ -133,12 +135,13 @@ projCtx_t::projCtx_t(const projCtx_t& other)
debug_level = other.debug_level;
logger = other.logger;
logger_app_data = other.logger_app_data;
- fileapi = other.fileapi;
+ fileapi_legacy = other.fileapi_legacy;
epsg_file_exists = other.epsg_file_exists;
set_search_paths(other.search_paths);
file_finder = other.file_finder;
file_finder_legacy = other.file_finder_legacy;
file_finder_user_data = other.file_finder_user_data;
+ networking = other.networking;
}
/************************************************************************/
@@ -258,28 +261,3 @@ void *pj_ctx_get_app_data( projCtx ctx )
return nullptr;
return ctx->logger_app_data;
}
-
-/************************************************************************/
-/* pj_ctx_set_fileapi() */
-/************************************************************************/
-
-void pj_ctx_set_fileapi( projCtx ctx, projFileAPI *fileapi )
-
-{
- if (nullptr==ctx)
- return;
- ctx->fileapi = fileapi;
-}
-
-/************************************************************************/
-/* pj_ctx_get_fileapi() */
-/************************************************************************/
-
-projFileAPI *pj_ctx_get_fileapi( projCtx ctx )
-
-{
- if (nullptr==ctx)
- return nullptr;
- return ctx->fileapi;
-}
-
diff --git a/src/datum_set.cpp b/src/datum_set.cpp
index 873d7be5..15d51613 100644
--- a/src/datum_set.cpp
+++ b/src/datum_set.cpp
@@ -41,7 +41,7 @@
int pj_datum_set(projCtx ctx, paralist *pl, PJ *projdef)
{
- const char *name, *towgs84, *nadgrids, *catalog;
+ const char *name, *towgs84, *nadgrids;
projdef->datum_type = PJD_UNKNOWN;
@@ -118,25 +118,6 @@ int pj_datum_set(projCtx ctx, paralist *pl, PJ *projdef)
}
/* -------------------------------------------------------------------- */
-/* Check for grid catalog parameter, and optional date. */
-/* -------------------------------------------------------------------- */
- else if( (catalog = pj_param(ctx, pl,"scatalog").s) != nullptr )
- {
- const char *date;
-
- projdef->datum_type = PJD_GRIDSHIFT;
- projdef->catalog_name = pj_strdup(catalog);
- if (!projdef->catalog_name) {
- pj_ctx_set_errno(ctx, ENOMEM);
- return 1;
- }
-
- date = pj_param(ctx, pl, "sdate").s;
- if( date != nullptr)
- projdef->datum_date = pj_gc_parsedate( ctx, date);
- }
-
-/* -------------------------------------------------------------------- */
/* Check for towgs84 parameter. */
/* -------------------------------------------------------------------- */
else if( (towgs84 = pj_param(ctx, pl,"stowgs84").s) != nullptr )
diff --git a/src/fileapi.cpp b/src/fileapi.cpp
index 70c7b5de..70be2502 100644
--- a/src/fileapi.cpp
+++ b/src/fileapi.cpp
@@ -34,6 +34,7 @@
#include "proj.h"
#include "proj_internal.h"
+#include "filemanager.hpp"
static PAFile stdio_fopen(projCtx ctx, const char *filename,
const char *access);
@@ -141,7 +142,7 @@ static void stdio_fclose(PAFile file)
PAFile pj_ctx_fopen(projCtx ctx, const char *filename, const char *access)
{
- return ctx->fileapi->FOpen(ctx, filename, access);
+ return ctx->fileapi_legacy->FOpen(ctx, filename, access);
}
/************************************************************************/
@@ -149,7 +150,7 @@ PAFile pj_ctx_fopen(projCtx ctx, const char *filename, const char *access)
/************************************************************************/
size_t pj_ctx_fread(projCtx ctx, void *buffer, size_t size, size_t nmemb, PAFile file)
{
- return ctx->fileapi->FRead(buffer, size, nmemb, file);
+ return ctx->fileapi_legacy->FRead(buffer, size, nmemb, file);
}
/************************************************************************/
@@ -157,7 +158,7 @@ size_t pj_ctx_fread(projCtx ctx, void *buffer, size_t size, size_t nmemb, PAFile
/************************************************************************/
int pj_ctx_fseek(projCtx ctx, PAFile file, long offset, int whence)
{
- return ctx->fileapi->FSeek(file, offset, whence);
+ return ctx->fileapi_legacy->FSeek(file, offset, whence);
}
/************************************************************************/
@@ -165,7 +166,7 @@ int pj_ctx_fseek(projCtx ctx, PAFile file, long offset, int whence)
/************************************************************************/
long pj_ctx_ftell(projCtx ctx, PAFile file)
{
- return ctx->fileapi->FTell(file);
+ return ctx->fileapi_legacy->FTell(file);
}
/************************************************************************/
@@ -173,7 +174,7 @@ long pj_ctx_ftell(projCtx ctx, PAFile file)
/************************************************************************/
void pj_ctx_fclose(projCtx ctx, PAFile file)
{
- ctx->fileapi->FClose(file);
+ ctx->fileapi_legacy->FClose(file);
}
/************************************************************************/
@@ -212,3 +213,28 @@ char *pj_ctx_fgets(projCtx ctx, char *line, int size, PAFile file)
}
return line;
}
+
+/************************************************************************/
+/* pj_ctx_set_fileapi() */
+/************************************************************************/
+
+void pj_ctx_set_fileapi( projCtx ctx, projFileAPI *fileapi )
+
+{
+ if (nullptr==ctx)
+ return;
+ ctx->fileapi_legacy = fileapi;
+}
+
+/************************************************************************/
+/* pj_ctx_get_fileapi() */
+/************************************************************************/
+
+projFileAPI *pj_ctx_get_fileapi( projCtx ctx )
+
+{
+ if (nullptr==ctx)
+ return nullptr;
+ return ctx->fileapi_legacy;
+}
+
diff --git a/src/filemanager.cpp b/src/filemanager.cpp
new file mode 100644
index 00000000..005e734b
--- /dev/null
+++ b/src/filemanager.cpp
@@ -0,0 +1,1733 @@
+/******************************************************************************
+ * Project: PROJ
+ * Purpose: File manager
+ * Author: Even Rouault, <even.rouault at spatialys.com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2019, Even Rouault, <even.rouault at spatialys.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *****************************************************************************/
+
+#ifndef FROM_PROJ_CPP
+#define FROM_PROJ_CPP
+#endif
+#define LRU11_DO_NOT_DEFINE_OUT_OF_CLASS_METHODS
+
+#include <errno.h>
+#include <stdlib.h>
+
+#include <algorithm>
+#include <limits>
+#include <string>
+
+#include "filemanager.hpp"
+#include "proj.h"
+#include "proj/internal/internal.hpp"
+#include "proj_internal.h"
+
+#include <sys/stat.h>
+
+#ifdef _WIN32
+#include <shlobj.h>
+#else
+#include <sys/types.h>
+#include <unistd.h>
+#endif
+
+//! @cond Doxygen_Suppress
+
+#define STR_HELPER(x) #x
+#define STR(x) STR_HELPER(x)
+
+using namespace NS_PROJ::internal;
+
+NS_PROJ_START
+
+// ---------------------------------------------------------------------------
+
+File::File(const std::string &name) : name_(name) {}
+
+// ---------------------------------------------------------------------------
+
+File::~File() = default;
+
+#ifdef _WIN32
+
+/* The bulk of utf8towc()/utf8fromwc() is derived from the utf.c module from
+ * FLTK. It was originally downloaded from:
+ * http://svn.easysw.com/public/fltk/fltk/trunk/src/utf.c
+ * And already used by GDAL
+ */
+/************************************************************************/
+/* ==================================================================== */
+/* UTF.C code from FLTK with some modifications. */
+/* ==================================================================== */
+/************************************************************************/
+
+/* Set to 1 to turn bad UTF8 bytes into ISO-8859-1. If this is to zero
+ they are instead turned into the Unicode REPLACEMENT CHARACTER, of
+ value 0xfffd.
+ If this is on utf8decode will correctly map most (perhaps all)
+ human-readable text that is in ISO-8859-1. This may allow you
+ to completely ignore character sets in your code because virtually
+ everything is either ISO-8859-1 or UTF-8.
+*/
+#define ERRORS_TO_ISO8859_1 1
+
+/* Set to 1 to turn bad UTF8 bytes in the 0x80-0x9f range into the
+ Unicode index for Microsoft's CP1252 character set. You should
+ also set ERRORS_TO_ISO8859_1. With this a huge amount of more
+ available text (such as all web pages) are correctly converted
+ to Unicode.
+*/
+#define ERRORS_TO_CP1252 1
+
+/* A number of Unicode code points are in fact illegal and should not
+ be produced by a UTF-8 converter. Turn this on will replace the
+ bytes in those encodings with errors. If you do this then converting
+ arbitrary 16-bit data to UTF-8 and then back is not an identity,
+ which will probably break a lot of software.
+*/
+#define STRICT_RFC3629 0
+
+#if ERRORS_TO_CP1252
+// Codes 0x80..0x9f from the Microsoft CP1252 character set, translated
+// to Unicode:
+constexpr unsigned short cp1252[32] = {
+ 0x20ac, 0x0081, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021,
+ 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008d, 0x017d, 0x008f,
+ 0x0090, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014,
+ 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x009d, 0x017e, 0x0178};
+#endif
+
+/************************************************************************/
+/* utf8decode() */
+/************************************************************************/
+
+/*
+ Decode a single UTF-8 encoded character starting at \e p. The
+ resulting Unicode value (in the range 0-0x10ffff) is returned,
+ and \e len is set the number of bytes in the UTF-8 encoding
+ (adding \e len to \e p will point at the next character).
+
+ If \a p points at an illegal UTF-8 encoding, including one that
+ would go past \e end, or where a code is uses more bytes than
+ necessary, then *reinterpret_cast<const unsigned char*>(p) is translated as
+though it is
+ in the Microsoft CP1252 character set and \e len is set to 1.
+ Treating errors this way allows this to decode almost any
+ ISO-8859-1 or CP1252 text that has been mistakenly placed where
+ UTF-8 is expected, and has proven very useful.
+
+ If you want errors to be converted to error characters (as the
+ standards recommend), adding a test to see if the length is
+ unexpectedly 1 will work:
+
+\code
+ if( *p & 0x80 )
+ { // What should be a multibyte encoding.
+ code = utf8decode(p, end, &len);
+ if( len<2 ) code = 0xFFFD; // Turn errors into REPLACEMENT CHARACTER.
+ }
+ else
+ { // Handle the 1-byte utf8 encoding:
+ code = *p;
+ len = 1;
+ }
+\endcode
+
+ Direct testing for the 1-byte case (as shown above) will also
+ speed up the scanning of strings where the majority of characters
+ are ASCII.
+*/
+static unsigned utf8decode(const char *p, const char *end, int *len) {
+ unsigned char c = *reinterpret_cast<const unsigned char *>(p);
+ if (c < 0x80) {
+ *len = 1;
+ return c;
+#if ERRORS_TO_CP1252
+ } else if (c < 0xa0) {
+ *len = 1;
+ return cp1252[c - 0x80];
+#endif
+ } else if (c < 0xc2) {
+ goto FAIL;
+ }
+ if (p + 1 >= end || (p[1] & 0xc0) != 0x80)
+ goto FAIL;
+ if (c < 0xe0) {
+ *len = 2;
+ return ((p[0] & 0x1f) << 6) + ((p[1] & 0x3f));
+ } else if (c == 0xe0) {
+ if ((reinterpret_cast<const unsigned char *>(p))[1] < 0xa0)
+ goto FAIL;
+ goto UTF8_3;
+#if STRICT_RFC3629
+ } else if (c == 0xed) {
+ // RFC 3629 says surrogate chars are illegal.
+ if ((reinterpret_cast<const unsigned char *>(p))[1] >= 0xa0)
+ goto FAIL;
+ goto UTF8_3;
+ } else if (c == 0xef) {
+ // 0xfffe and 0xffff are also illegal characters.
+ if ((reinterpret_cast<const unsigned char *>(p))[1] == 0xbf &&
+ (reinterpret_cast<const unsigned char *>(p))[2] >= 0xbe)
+ goto FAIL;
+ goto UTF8_3;
+#endif
+ } else if (c < 0xf0) {
+ UTF8_3:
+ if (p + 2 >= end || (p[2] & 0xc0) != 0x80)
+ goto FAIL;
+ *len = 3;
+ return ((p[0] & 0x0f) << 12) + ((p[1] & 0x3f) << 6) + ((p[2] & 0x3f));
+ } else if (c == 0xf0) {
+ if ((reinterpret_cast<const unsigned char *>(p))[1] < 0x90)
+ goto FAIL;
+ goto UTF8_4;
+ } else if (c < 0xf4) {
+ UTF8_4:
+ if (p + 3 >= end || (p[2] & 0xc0) != 0x80 || (p[3] & 0xc0) != 0x80)
+ goto FAIL;
+ *len = 4;
+#if STRICT_RFC3629
+ // RFC 3629 says all codes ending in fffe or ffff are illegal:
+ if ((p[1] & 0xf) == 0xf &&
+ (reinterpret_cast<const unsigned char *>(p))[2] == 0xbf &&
+ (reinterpret_cast<const unsigned char *>(p))[3] >= 0xbe)
+ goto FAIL;
+#endif
+ return ((p[0] & 0x07) << 18) + ((p[1] & 0x3f) << 12) +
+ ((p[2] & 0x3f) << 6) + ((p[3] & 0x3f));
+ } else if (c == 0xf4) {
+ if ((reinterpret_cast<const unsigned char *>(p))[1] > 0x8f)
+ goto FAIL; // After 0x10ffff.
+ goto UTF8_4;
+ } else {
+ FAIL:
+ *len = 1;
+#if ERRORS_TO_ISO8859_1
+ return c;
+#else
+ return 0xfffd; // Unicode REPLACEMENT CHARACTER
+#endif
+ }
+}
+
+/************************************************************************/
+/* utf8towc() */
+/************************************************************************/
+
+/* Convert a UTF-8 sequence into an array of wchar_t. These
+ are used by some system calls, especially on Windows.
+
+ \a src points at the UTF-8, and \a srclen is the number of bytes to
+ convert.
+
+ \a dst points at an array to write, and \a dstlen is the number of
+ locations in this array. At most \a dstlen-1 words will be
+ written there, plus a 0 terminating word. Thus this function
+ will never overwrite the buffer and will always return a
+ zero-terminated string. If \a dstlen is zero then \a dst can be
+ null and no data is written, but the length is returned.
+
+ The return value is the number of words that \e would be written
+ to \a dst if it were long enough, not counting the terminating
+ zero. If the return value is greater or equal to \a dstlen it
+ indicates truncation, you can then allocate a new array of size
+ return+1 and call this again.
+
+ Errors in the UTF-8 are converted as though each byte in the
+ erroneous string is in the Microsoft CP1252 encoding. This allows
+ ISO-8859-1 text mistakenly identified as UTF-8 to be printed
+ correctly.
+
+ Notice that sizeof(wchar_t) is 2 on Windows and is 4 on Linux
+ and most other systems. Where wchar_t is 16 bits, Unicode
+ characters in the range 0x10000 to 0x10ffff are converted to
+ "surrogate pairs" which take two words each (this is called UTF-16
+ encoding). If wchar_t is 32 bits this rather nasty problem is
+ avoided.
+*/
+static unsigned utf8towc(const char *src, unsigned srclen, wchar_t *dst,
+ unsigned dstlen) {
+ const char *p = src;
+ const char *e = src + srclen;
+ unsigned count = 0;
+ if (dstlen)
+ while (true) {
+ if (p >= e) {
+ dst[count] = 0;
+ return count;
+ }
+ if (!(*p & 0x80)) {
+ // ASCII
+ dst[count] = *p++;
+ } else {
+ int len = 0;
+ unsigned ucs = utf8decode(p, e, &len);
+ p += len;
+#ifdef _WIN32
+ if (ucs < 0x10000) {
+ dst[count] = static_cast<wchar_t>(ucs);
+ } else {
+ // Make a surrogate pair:
+ if (count + 2 >= dstlen) {
+ dst[count] = 0;
+ count += 2;
+ break;
+ }
+ dst[count] = static_cast<wchar_t>(
+ (((ucs - 0x10000u) >> 10) & 0x3ff) | 0xd800);
+ dst[++count] = static_cast<wchar_t>((ucs & 0x3ff) | 0xdc00);
+ }
+#else
+ dst[count] = static_cast<wchar_t>(ucs);
+#endif
+ }
+ if (++count == dstlen) {
+ dst[count - 1] = 0;
+ break;
+ }
+ }
+ // We filled dst, measure the rest:
+ while (p < e) {
+ if (!(*p & 0x80)) {
+ p++;
+ } else {
+ int len = 0;
+#ifdef _WIN32
+ const unsigned ucs = utf8decode(p, e, &len);
+ p += len;
+ if (ucs >= 0x10000)
+ ++count;
+#else
+ utf8decode(p, e, &len);
+ p += len;
+#endif
+ }
+ ++count;
+ }
+
+ return count;
+}
+
+// ---------------------------------------------------------------------------
+
+struct NonValidUTF8Exception : public std::exception {};
+
+// May throw exceptions
+static std::wstring UTF8ToWString(const std::string &str) {
+ std::wstring wstr;
+ wstr.resize(str.size());
+ wstr.resize(utf8towc(str.data(), static_cast<unsigned>(str.size()),
+ &wstr[0], static_cast<unsigned>(wstr.size()) + 1));
+ for (const auto ch : wstr) {
+ if (ch == 0xfffd) {
+ throw NonValidUTF8Exception();
+ }
+ }
+ return wstr;
+}
+
+// ---------------------------------------------------------------------------
+
+/************************************************************************/
+/* utf8fromwc() */
+/************************************************************************/
+/* Turn "wide characters" as returned by some system calls
+ (especially on Windows) into UTF-8.
+
+ Up to \a dstlen bytes are written to \a dst, including a null
+ terminator. The return value is the number of bytes that would be
+ written, not counting the null terminator. If greater or equal to
+ \a dstlen then if you malloc a new array of size n+1 you will have
+ the space needed for the entire string. If \a dstlen is zero then
+ nothing is written and this call just measures the storage space
+ needed.
+
+ \a srclen is the number of words in \a src to convert. On Windows
+ this is not necessarily the number of characters, due to there
+ possibly being "surrogate pairs" in the UTF-16 encoding used.
+ On Unix wchar_t is 32 bits and each location is a character.
+
+ On Unix if a src word is greater than 0x10ffff then this is an
+ illegal character according to RFC 3629. These are converted as
+ though they are 0xFFFD (REPLACEMENT CHARACTER). Characters in the
+ range 0xd800 to 0xdfff, or ending with 0xfffe or 0xffff are also
+ illegal according to RFC 3629. However I encode these as though
+ they are legal, so that utf8towc will return the original data.
+
+ On Windows "surrogate pairs" are converted to a single character
+ and UTF-8 encoded (as 4 bytes). Mismatched halves of surrogate
+ pairs are converted as though they are individual characters.
+*/
+static unsigned int utf8fromwc(char *dst, unsigned dstlen, const wchar_t *src,
+ unsigned srclen) {
+ unsigned int i = 0;
+ unsigned int count = 0;
+ if (dstlen)
+ while (true) {
+ if (i >= srclen) {
+ dst[count] = 0;
+ return count;
+ }
+ unsigned int ucs = src[i++];
+ if (ucs < 0x80U) {
+ dst[count++] = static_cast<char>(ucs);
+ if (count >= dstlen) {
+ dst[count - 1] = 0;
+ break;
+ }
+ } else if (ucs < 0x800U) {
+ // 2 bytes.
+ if (count + 2 >= dstlen) {
+ dst[count] = 0;
+ count += 2;
+ break;
+ }
+ dst[count++] = 0xc0 | static_cast<char>(ucs >> 6);
+ dst[count++] = 0x80 | static_cast<char>(ucs & 0x3F);
+#ifdef _WIN32
+ } else if (ucs >= 0xd800 && ucs <= 0xdbff && i < srclen &&
+ src[i] >= 0xdc00 && src[i] <= 0xdfff) {
+ // Surrogate pair.
+ unsigned int ucs2 = src[i++];
+ ucs = 0x10000U + ((ucs & 0x3ff) << 10) + (ucs2 & 0x3ff);
+// All surrogate pairs turn into 4-byte utf8.
+#else
+ } else if (ucs >= 0x10000) {
+ if (ucs > 0x10ffff) {
+ ucs = 0xfffd;
+ goto J1;
+ }
+#endif
+ if (count + 4 >= dstlen) {
+ dst[count] = 0;
+ count += 4;
+ break;
+ }
+ dst[count++] = 0xf0 | static_cast<char>(ucs >> 18);
+ dst[count++] = 0x80 | static_cast<char>((ucs >> 12) & 0x3F);
+ dst[count++] = 0x80 | static_cast<char>((ucs >> 6) & 0x3F);
+ dst[count++] = 0x80 | static_cast<char>(ucs & 0x3F);
+ } else {
+#ifndef _WIN32
+ J1:
+#endif
+ // All others are 3 bytes:
+ if (count + 3 >= dstlen) {
+ dst[count] = 0;
+ count += 3;
+ break;
+ }
+ dst[count++] = 0xe0 | static_cast<char>(ucs >> 12);
+ dst[count++] = 0x80 | static_cast<char>((ucs >> 6) & 0x3F);
+ dst[count++] = 0x80 | static_cast<char>(ucs & 0x3F);
+ }
+ }
+
+ // We filled dst, measure the rest:
+ while (i < srclen) {
+ unsigned int ucs = src[i++];
+ if (ucs < 0x80U) {
+ count++;
+ } else if (ucs < 0x800U) {
+ // 2 bytes.
+ count += 2;
+#ifdef _WIN32
+ } else if (ucs >= 0xd800 && ucs <= 0xdbff && i < srclen - 1 &&
+ src[i + 1] >= 0xdc00 && src[i + 1] <= 0xdfff) {
+ // Surrogate pair.
+ ++i;
+#else
+ } else if (ucs >= 0x10000 && ucs <= 0x10ffff) {
+#endif
+ count += 4;
+ } else {
+ count += 3;
+ }
+ }
+ return count;
+}
+
+// ---------------------------------------------------------------------------
+
+static std::string WStringToUTF8(const std::wstring &wstr) {
+ std::string str;
+ str.resize(wstr.size());
+ str.resize(utf8fromwc(&str[0], static_cast<unsigned>(str.size() + 1),
+ wstr.data(), static_cast<unsigned>(wstr.size())));
+ return str;
+}
+
+// ---------------------------------------------------------------------------
+
+static std::string Win32Recode(const char *src, unsigned src_code_page,
+ unsigned dst_code_page) {
+ // Convert from source code page to Unicode.
+
+ // Compute the length in wide characters.
+ int wlen = MultiByteToWideChar(src_code_page, MB_ERR_INVALID_CHARS, src, -1,
+ nullptr, 0);
+ if (wlen == 0 && GetLastError() == ERROR_NO_UNICODE_TRANSLATION) {
+ return std::string();
+ }
+
+ // Do the actual conversion.
+ std::wstring wbuf;
+ wbuf.resize(wlen);
+ MultiByteToWideChar(src_code_page, 0, src, -1, &wbuf[0], wlen);
+
+ // Convert from Unicode to destination code page.
+
+ // Compute the length in chars.
+ int len = WideCharToMultiByte(dst_code_page, 0, &wbuf[0], -1, nullptr, 0,
+ nullptr, nullptr);
+
+ // Do the actual conversion.
+ std::string out;
+ out.resize(len);
+ WideCharToMultiByte(dst_code_page, 0, &wbuf[0], -1, &out[0], len, nullptr,
+ nullptr);
+ out.resize(strlen(out.c_str()));
+
+ return out;
+}
+
+// ---------------------------------------------------------------------------
+
+class FileWin32 : public File {
+ PJ_CONTEXT *m_ctx;
+ HANDLE m_handle;
+
+ FileWin32(const FileWin32 &) = delete;
+ FileWin32 &operator=(const FileWin32 &) = delete;
+
+ protected:
+ FileWin32(const std::string &name, PJ_CONTEXT *ctx, HANDLE handle)
+ : File(name), m_ctx(ctx), m_handle(handle) {}
+
+ public:
+ ~FileWin32() override;
+
+ size_t read(void *buffer, size_t sizeBytes) override;
+ size_t write(const void *buffer, size_t sizeBytes) override;
+ bool seek(unsigned long long offset, int whence = SEEK_SET) override;
+ unsigned long long tell() override;
+ void reassign_context(PJ_CONTEXT *ctx) override { m_ctx = ctx; }
+
+ // We may lie, but the real use case is only for network files
+ bool hasChanged() const override { return false; }
+
+ static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename,
+ FileAccess access);
+};
+
+// ---------------------------------------------------------------------------
+
+FileWin32::~FileWin32() { CloseHandle(m_handle); }
+
+// ---------------------------------------------------------------------------
+
+size_t FileWin32::read(void *buffer, size_t sizeBytes) {
+ DWORD dwSizeRead = 0;
+ size_t nResult = 0;
+
+ if (!ReadFile(m_handle, buffer, static_cast<DWORD>(sizeBytes), &dwSizeRead,
+ nullptr))
+ nResult = 0;
+ else
+ nResult = dwSizeRead;
+
+ return nResult;
+}
+
+// ---------------------------------------------------------------------------
+
+size_t FileWin32::write(const void *buffer, size_t sizeBytes) {
+ DWORD dwSizeWritten = 0;
+ size_t nResult = 0;
+
+ if (!WriteFile(m_handle, buffer, static_cast<DWORD>(sizeBytes),
+ &dwSizeWritten, nullptr))
+ nResult = 0;
+ else
+ nResult = dwSizeWritten;
+
+ return nResult;
+}
+
+// ---------------------------------------------------------------------------
+
+bool FileWin32::seek(unsigned long long offset, int whence) {
+ LONG dwMoveMethod, dwMoveHigh;
+ uint32_t nMoveLow;
+ LARGE_INTEGER li;
+
+ switch (whence) {
+ case SEEK_CUR:
+ dwMoveMethod = FILE_CURRENT;
+ break;
+ case SEEK_END:
+ dwMoveMethod = FILE_END;
+ break;
+ case SEEK_SET:
+ default:
+ dwMoveMethod = FILE_BEGIN;
+ break;
+ }
+
+ li.QuadPart = offset;
+ nMoveLow = li.LowPart;
+ dwMoveHigh = li.HighPart;
+
+ SetLastError(0);
+ SetFilePointer(m_handle, nMoveLow, &dwMoveHigh, dwMoveMethod);
+
+ return GetLastError() == NO_ERROR;
+}
+
+// ---------------------------------------------------------------------------
+
+unsigned long long FileWin32::tell() {
+ LARGE_INTEGER li;
+
+ li.HighPart = 0;
+ li.LowPart = SetFilePointer(m_handle, 0, &(li.HighPart), FILE_CURRENT);
+
+ return static_cast<unsigned long long>(li.QuadPart);
+}
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<File> FileWin32::open(PJ_CONTEXT *ctx, const char *filename,
+ FileAccess access) {
+ DWORD dwDesiredAccess = access == FileAccess::READ_ONLY
+ ? GENERIC_READ
+ : GENERIC_READ | GENERIC_WRITE;
+ DWORD dwCreationDisposition =
+ access == FileAccess::CREATE ? CREATE_ALWAYS : OPEN_EXISTING;
+ DWORD dwFlagsAndAttributes = (dwDesiredAccess == GENERIC_READ)
+ ? FILE_ATTRIBUTE_READONLY
+ : FILE_ATTRIBUTE_NORMAL;
+ try {
+ HANDLE hFile = CreateFileW(
+ UTF8ToWString(std::string(filename)).c_str(), dwDesiredAccess,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
+ dwCreationDisposition, dwFlagsAndAttributes, nullptr);
+ return std::unique_ptr<File>(hFile != INVALID_HANDLE_VALUE
+ ? new FileWin32(filename, ctx, hFile)
+ : nullptr);
+ } catch (const std::exception &e) {
+ pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what());
+ return nullptr;
+ }
+}
+#else
+
+// ---------------------------------------------------------------------------
+
+class FileStdio : public File {
+ PJ_CONTEXT *m_ctx;
+ FILE *m_fp;
+
+ FileStdio(const FileStdio &) = delete;
+ FileStdio &operator=(const FileStdio &) = delete;
+
+ protected:
+ FileStdio(const std::string &name, PJ_CONTEXT *ctx, FILE *fp)
+ : File(name), m_ctx(ctx), m_fp(fp) {}
+
+ public:
+ ~FileStdio() override;
+
+ size_t read(void *buffer, size_t sizeBytes) override;
+ size_t write(const void *buffer, size_t sizeBytes) override;
+ bool seek(unsigned long long offset, int whence = SEEK_SET) override;
+ unsigned long long tell() override;
+ void reassign_context(PJ_CONTEXT *ctx) override { m_ctx = ctx; }
+
+ // We may lie, but the real use case is only for network files
+ bool hasChanged() const override { return false; }
+
+ static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename,
+ FileAccess access);
+};
+
+// ---------------------------------------------------------------------------
+
+FileStdio::~FileStdio() { fclose(m_fp); }
+
+// ---------------------------------------------------------------------------
+
+size_t FileStdio::read(void *buffer, size_t sizeBytes) {
+ return fread(buffer, 1, sizeBytes, m_fp);
+}
+
+// ---------------------------------------------------------------------------
+
+size_t FileStdio::write(const void *buffer, size_t sizeBytes) {
+ return fwrite(buffer, 1, sizeBytes, m_fp);
+}
+
+// ---------------------------------------------------------------------------
+
+bool FileStdio::seek(unsigned long long offset, int whence) {
+ // TODO one day: use 64-bit offset compatible API
+ if (offset != static_cast<unsigned long long>(static_cast<long>(offset))) {
+ pj_log(m_ctx, PJ_LOG_ERROR,
+ "Attempt at seeking to a 64 bit offset. Not supported yet");
+ return false;
+ }
+ return fseek(m_fp, static_cast<long>(offset), whence) == 0;
+}
+
+// ---------------------------------------------------------------------------
+
+unsigned long long FileStdio::tell() {
+ // TODO one day: use 64-bit offset compatible API
+ return ftell(m_fp);
+}
+
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<File> FileStdio::open(PJ_CONTEXT *ctx, const char *filename,
+ FileAccess access) {
+ auto fp = fopen(filename,
+ access == FileAccess::READ_ONLY
+ ? "rb"
+ : access == FileAccess::READ_UPDATE ? "r+b" : "w+b");
+ return std::unique_ptr<File>(fp ? new FileStdio(filename, ctx, fp)
+ : nullptr);
+}
+
+#endif // _WIN32
+
+// ---------------------------------------------------------------------------
+
+#ifndef REMOVE_LEGACY_SUPPORT
+
+class FileLegacyAdapter : public File {
+ PJ_CONTEXT *m_ctx;
+ PAFile m_fp;
+
+ FileLegacyAdapter(const FileLegacyAdapter &) = delete;
+ FileLegacyAdapter &operator=(const FileLegacyAdapter &) = delete;
+
+ protected:
+ FileLegacyAdapter(const std::string &name, PJ_CONTEXT *ctx, PAFile fp)
+ : File(name), m_ctx(ctx), m_fp(fp) {}
+
+ public:
+ ~FileLegacyAdapter() override;
+
+ size_t read(void *buffer, size_t sizeBytes) override;
+ size_t write(const void *, size_t) override { return 0; }
+ bool seek(unsigned long long offset, int whence = SEEK_SET) override;
+ unsigned long long tell() override;
+ void reassign_context(PJ_CONTEXT *ctx) override { m_ctx = ctx; }
+
+ // We may lie, but the real use case is only for network files
+ bool hasChanged() const override { return false; }
+
+ static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename,
+ FileAccess access);
+};
+
+// ---------------------------------------------------------------------------
+
+FileLegacyAdapter::~FileLegacyAdapter() { pj_ctx_fclose(m_ctx, m_fp); }
+
+// ---------------------------------------------------------------------------
+
+size_t FileLegacyAdapter::read(void *buffer, size_t sizeBytes) {
+ return pj_ctx_fread(m_ctx, buffer, 1, sizeBytes, m_fp);
+}
+
+// ---------------------------------------------------------------------------
+
+bool FileLegacyAdapter::seek(unsigned long long offset, int whence) {
+ if (offset != static_cast<unsigned long long>(static_cast<long>(offset))) {
+ pj_log(m_ctx, PJ_LOG_ERROR,
+ "Attempt at seeking to a 64 bit offset. Not supported yet");
+ return false;
+ }
+ return pj_ctx_fseek(m_ctx, m_fp, static_cast<long>(offset), whence) == 0;
+}
+
+// ---------------------------------------------------------------------------
+
+unsigned long long FileLegacyAdapter::tell() {
+ return pj_ctx_ftell(m_ctx, m_fp);
+}
+
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<File>
+FileLegacyAdapter::open(PJ_CONTEXT *ctx, const char *filename, FileAccess) {
+ auto fid = pj_ctx_fopen(ctx, filename, "rb");
+ return std::unique_ptr<File>(fid ? new FileLegacyAdapter(filename, ctx, fid)
+ : nullptr);
+}
+
+#endif // REMOVE_LEGACY_SUPPORT
+
+// ---------------------------------------------------------------------------
+
+class FileApiAdapter : public File {
+ PJ_CONTEXT *m_ctx;
+ PROJ_FILE_HANDLE *m_fp;
+
+ FileApiAdapter(const FileApiAdapter &) = delete;
+ FileApiAdapter &operator=(const FileApiAdapter &) = delete;
+
+ protected:
+ FileApiAdapter(const std::string &name, PJ_CONTEXT *ctx,
+ PROJ_FILE_HANDLE *fp)
+ : File(name), m_ctx(ctx), m_fp(fp) {}
+
+ public:
+ ~FileApiAdapter() override;
+
+ size_t read(void *buffer, size_t sizeBytes) override;
+ size_t write(const void *, size_t) override;
+ bool seek(unsigned long long offset, int whence = SEEK_SET) override;
+ unsigned long long tell() override;
+ void reassign_context(PJ_CONTEXT *ctx) override { m_ctx = ctx; }
+
+ // We may lie, but the real use case is only for network files
+ bool hasChanged() const override { return false; }
+
+ static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename,
+ FileAccess access);
+};
+
+// ---------------------------------------------------------------------------
+
+FileApiAdapter::~FileApiAdapter() {
+ m_ctx->fileApi.close_cbk(m_ctx, m_fp, m_ctx->fileApi.user_data);
+}
+
+// ---------------------------------------------------------------------------
+
+size_t FileApiAdapter::read(void *buffer, size_t sizeBytes) {
+ return m_ctx->fileApi.read_cbk(m_ctx, m_fp, buffer, sizeBytes,
+ m_ctx->fileApi.user_data);
+}
+
+// ---------------------------------------------------------------------------
+
+size_t FileApiAdapter::write(const void *buffer, size_t sizeBytes) {
+ return m_ctx->fileApi.write_cbk(m_ctx, m_fp, buffer, sizeBytes,
+ m_ctx->fileApi.user_data);
+}
+
+// ---------------------------------------------------------------------------
+
+bool FileApiAdapter::seek(unsigned long long offset, int whence) {
+ return m_ctx->fileApi.seek_cbk(m_ctx, m_fp, static_cast<long long>(offset),
+ whence, m_ctx->fileApi.user_data) != 0;
+}
+
+// ---------------------------------------------------------------------------
+
+unsigned long long FileApiAdapter::tell() {
+ return m_ctx->fileApi.tell_cbk(m_ctx, m_fp, m_ctx->fileApi.user_data);
+}
+
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<File> FileApiAdapter::open(PJ_CONTEXT *ctx,
+ const char *filename,
+ FileAccess eAccess) {
+ PROJ_OPEN_ACCESS eCAccess = PROJ_OPEN_ACCESS_READ_ONLY;
+ switch (eAccess) {
+ case FileAccess::READ_ONLY:
+ // Initialized above
+ break;
+ case FileAccess::READ_UPDATE:
+ eCAccess = PROJ_OPEN_ACCESS_READ_UPDATE;
+ break;
+ case FileAccess::CREATE:
+ eCAccess = PROJ_OPEN_ACCESS_CREATE;
+ break;
+ }
+ auto fp =
+ ctx->fileApi.open_cbk(ctx, filename, eCAccess, ctx->fileApi.user_data);
+ return std::unique_ptr<File>(fp ? new FileApiAdapter(filename, ctx, fp)
+ : nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<File> FileManager::open(PJ_CONTEXT *ctx, const char *filename,
+ FileAccess access) {
+ if (starts_with(filename, "http://") || starts_with(filename, "https://")) {
+ if (!pj_context_is_network_enabled(ctx)) {
+ pj_log(
+ ctx, PJ_LOG_ERROR,
+ "Attempt at accessing remote resource not authorized. Either "
+ "set PROJ_NETWORK=ON or "
+ "proj_context_set_enable_network(ctx, TRUE)");
+ return nullptr;
+ }
+ return pj_network_file_open(ctx, filename);
+ }
+#ifndef REMOVE_LEGACY_SUPPORT
+ // If the user has specified a legacy fileapi, use it
+ if (ctx->fileapi_legacy != pj_get_default_fileapi()) {
+ return FileLegacyAdapter::open(ctx, filename, access);
+ }
+#endif
+ if (ctx->fileApi.open_cbk != nullptr) {
+ return FileApiAdapter::open(ctx, filename, access);
+ }
+#ifdef _WIN32
+ return FileWin32::open(ctx, filename, access);
+#else
+ return FileStdio::open(ctx, filename, access);
+#endif
+}
+
+// ---------------------------------------------------------------------------
+
+bool FileManager::exists(PJ_CONTEXT *ctx, const char *filename) {
+ if (ctx->fileApi.exists_cbk) {
+ return ctx->fileApi.exists_cbk(ctx, filename, ctx->fileApi.user_data) !=
+ 0;
+ }
+
+#ifdef _WIN32
+ struct __stat64 buf;
+ try {
+ return _wstat64(UTF8ToWString(filename).c_str(), &buf) == 0;
+ } catch (const std::exception &e) {
+ pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what());
+ return false;
+ }
+#else
+ (void)ctx;
+ struct stat sStat;
+ return stat(filename, &sStat) == 0;
+#endif
+}
+
+// ---------------------------------------------------------------------------
+
+bool FileManager::mkdir(PJ_CONTEXT *ctx, const char *filename) {
+ if (ctx->fileApi.mkdir_cbk) {
+ return ctx->fileApi.mkdir_cbk(ctx, filename, ctx->fileApi.user_data) !=
+ 0;
+ }
+
+#ifdef _WIN32
+ try {
+ return _wmkdir(UTF8ToWString(filename).c_str()) == 0;
+ } catch (const std::exception &e) {
+ pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what());
+ return false;
+ }
+#else
+ (void)ctx;
+ return ::mkdir(filename, 0755) == 0;
+#endif
+}
+
+// ---------------------------------------------------------------------------
+
+bool FileManager::unlink(PJ_CONTEXT *ctx, const char *filename) {
+ if (ctx->fileApi.unlink_cbk) {
+ return ctx->fileApi.unlink_cbk(ctx, filename, ctx->fileApi.user_data) !=
+ 0;
+ }
+
+#ifdef _WIN32
+ try {
+ return _wunlink(UTF8ToWString(filename).c_str()) == 0;
+ } catch (const std::exception &e) {
+ pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what());
+ return false;
+ }
+#else
+ (void)ctx;
+ return ::unlink(filename) == 0;
+#endif
+}
+
+// ---------------------------------------------------------------------------
+
+bool FileManager::rename(PJ_CONTEXT *ctx, const char *oldPath,
+ const char *newPath) {
+ if (ctx->fileApi.rename_cbk) {
+ return ctx->fileApi.rename_cbk(ctx, oldPath, newPath,
+ ctx->fileApi.user_data) != 0;
+ }
+
+#ifdef _WIN32
+ try {
+ return _wrename(UTF8ToWString(oldPath).c_str(),
+ UTF8ToWString(newPath).c_str()) == 0;
+ } catch (const std::exception &e) {
+ pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what());
+ return false;
+ }
+#else
+ (void)ctx;
+ return ::rename(oldPath, newPath) == 0;
+#endif
+}
+
+// ---------------------------------------------------------------------------
+
+std::string FileManager::getProjLibEnvVar(PJ_CONTEXT *ctx) {
+ if (!ctx->env_var_proj_lib.empty()) {
+ return ctx->env_var_proj_lib;
+ }
+ (void)ctx;
+ std::string str;
+ const char *envvar = getenv("PROJ_LIB");
+ if (!envvar)
+ return str;
+ str = envvar;
+#ifdef _WIN32
+ // Assume this is UTF-8. If not try to convert from ANSI page
+ bool looksLikeUTF8 = false;
+ try {
+ UTF8ToWString(envvar);
+ looksLikeUTF8 = true;
+ } catch (const std::exception &) {
+ }
+ if (!looksLikeUTF8 || !exists(ctx, envvar)) {
+ str = Win32Recode(envvar, CP_ACP, CP_UTF8);
+ if (str.empty() || !exists(ctx, str.c_str()))
+ str = envvar;
+ }
+#endif
+ ctx->env_var_proj_lib = str;
+ return str;
+}
+
+NS_PROJ_END
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** Set a file API
+ *
+ * All callbacks should be provided (non NULL pointers). If read-only usage
+ * is intended, then the callbacks might have a dummy implementation.
+ *
+ * \note Those callbacks will not be used for SQLite3 database access. If
+ * custom I/O is desired for that, then proj_context_set_sqlite3_vfs_name()
+ * should be used.
+ *
+ * @param ctx PROJ context, or NULL
+ * @param fileapi Pointer to file API structure (content will be copied).
+ * @param user_data Arbitrary pointer provided by the user, and passed to the
+ * above callbacks. May be NULL.
+ * @return TRUE in case of success.
+ * @since 7.0
+ */
+int proj_context_set_fileapi(PJ_CONTEXT *ctx, const PROJ_FILE_API *fileapi,
+ void *user_data) {
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+ if (!fileapi) {
+ return false;
+ }
+ if (fileapi->version != 1) {
+ return false;
+ }
+ if (!fileapi->open_cbk || !fileapi->close_cbk || !fileapi->read_cbk ||
+ !fileapi->write_cbk || !fileapi->seek_cbk || !fileapi->tell_cbk ||
+ !fileapi->exists_cbk || !fileapi->mkdir_cbk || !fileapi->unlink_cbk ||
+ !fileapi->rename_cbk) {
+ return false;
+ }
+ ctx->fileApi.open_cbk = fileapi->open_cbk;
+ ctx->fileApi.close_cbk = fileapi->close_cbk;
+ ctx->fileApi.read_cbk = fileapi->read_cbk;
+ ctx->fileApi.write_cbk = fileapi->write_cbk;
+ ctx->fileApi.seek_cbk = fileapi->seek_cbk;
+ ctx->fileApi.tell_cbk = fileapi->tell_cbk;
+ ctx->fileApi.exists_cbk = fileapi->exists_cbk;
+ ctx->fileApi.mkdir_cbk = fileapi->mkdir_cbk;
+ ctx->fileApi.unlink_cbk = fileapi->unlink_cbk;
+ ctx->fileApi.rename_cbk = fileapi->rename_cbk;
+ ctx->fileApi.user_data = user_data;
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+/** Set the name of a custom SQLite3 VFS.
+ *
+ * This should be a valid SQLite3 VFS name, such as the one passed to the
+ * sqlite3_vfs_register(). See https://www.sqlite.org/vfs.html
+ *
+ * It will be used to read proj.db or create&access the cache.db file in the
+ * PROJ user writable directory.
+ *
+ * @param ctx PROJ context, or NULL
+ * @param name SQLite3 VFS name. If NULL is passed, default implementation by
+ * SQLite will be used.
+ * @since 7.0
+ */
+void proj_context_set_sqlite3_vfs_name(PJ_CONTEXT *ctx, const char *name) {
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+ ctx->custom_sqlite3_vfs_name = name ? name : std::string();
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+// ---------------------------------------------------------------------------
+
+static void CreateDirectoryRecursively(PJ_CONTEXT *ctx,
+ const std::string &path) {
+ if (NS_PROJ::FileManager::exists(ctx, path.c_str()))
+ return;
+ auto pos = path.find_last_of("/\\");
+ if (pos == 0 || pos == std::string::npos)
+ return;
+ CreateDirectoryRecursively(ctx, path.substr(0, pos));
+ NS_PROJ::FileManager::mkdir(ctx, path.c_str());
+}
+
+// ---------------------------------------------------------------------------
+
+std::string pj_context_get_user_writable_directory(PJ_CONTEXT *ctx,
+ bool create) {
+ if (ctx->user_writable_directory.empty()) {
+ // For testing purposes only
+ const char *env_var_PROJ_USER_WRITABLE_DIRECTORY =
+ getenv("PROJ_USER_WRITABLE_DIRECTORY");
+ if (env_var_PROJ_USER_WRITABLE_DIRECTORY &&
+ env_var_PROJ_USER_WRITABLE_DIRECTORY[0] != '\0') {
+ ctx->user_writable_directory = env_var_PROJ_USER_WRITABLE_DIRECTORY;
+ }
+ }
+ if (ctx->user_writable_directory.empty()) {
+ std::string path;
+#ifdef _WIN32
+ std::wstring wPath;
+ wPath.resize(MAX_PATH);
+ if (SHGetFolderPathW(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0,
+ &wPath[0]) == S_OK) {
+ wPath.resize(wcslen(wPath.data()));
+ path = NS_PROJ::WStringToUTF8(wPath);
+ } else {
+ const char *local_app_data = getenv("LOCALAPPDATA");
+ if (!local_app_data) {
+ local_app_data = getenv("TEMP");
+ if (!local_app_data) {
+ local_app_data = "c:/users";
+ }
+ }
+ path = local_app_data;
+ }
+#else
+ const char *xdg_data_home = getenv("XDG_DATA_HOME");
+ if (xdg_data_home != nullptr) {
+ path = xdg_data_home;
+ } else {
+ const char *home = getenv("HOME");
+ if (home) {
+#if defined(__MACH__) && defined(__APPLE__)
+ path = std::string(home) + "/Library/Application Support";
+#else
+ path = std::string(home) + "/.local/share";
+#endif
+ } else {
+ path = "/tmp";
+ }
+ }
+#endif
+ path += "/proj";
+ ctx->user_writable_directory = path;
+ }
+ if (create) {
+ CreateDirectoryRecursively(ctx, ctx->user_writable_directory);
+ }
+ return ctx->user_writable_directory;
+}
+
+// ---------------------------------------------------------------------------
+
+#ifdef WIN32
+static const char dir_chars[] = "/\\";
+#else
+static const char dir_chars[] = "/";
+#endif
+
+static bool is_tilde_slash(const char *name) {
+ return *name == '~' && strchr(dir_chars, name[1]);
+}
+
+static bool is_rel_or_absolute_filename(const char *name) {
+ return strchr(dir_chars, *name) ||
+ (*name == '.' && strchr(dir_chars, name[1])) ||
+ (!strncmp(name, "..", 2) && strchr(dir_chars, name[2])) ||
+ (name[0] != '\0' && name[1] == ':' && strchr(dir_chars, name[2]));
+}
+
+// ---------------------------------------------------------------------------
+
+#ifdef _WIN32
+static const char *get_path_from_win32_projlib(PJ_CONTEXT *ctx,
+ const char *name,
+ std::string &out) {
+ /* Check if proj.db lieves in a share/proj dir parallel to bin/proj.dll */
+ /* Based in
+ * https://stackoverflow.com/questions/9112893/how-to-get-path-to-executable-in-c-running-on-windows
+ */
+
+ DWORD path_size = 1024;
+
+ std::wstring wout;
+ for (;;) {
+ wout.clear();
+ wout.resize(path_size);
+ DWORD result = GetModuleFileNameW(nullptr, &wout[0], path_size - 1);
+ DWORD last_error = GetLastError();
+
+ if (result == 0) {
+ return nullptr;
+ } else if (result == path_size - 1) {
+ if (ERROR_INSUFFICIENT_BUFFER != last_error) {
+ return nullptr;
+ }
+ path_size = path_size * 2;
+ } else {
+ break;
+ }
+ }
+ // Now remove the program's name. It was (example)
+ // "C:\programs\gmt6\bin\gdal_translate.exe"
+ wout.resize(wcslen(wout.c_str()));
+ out = NS_PROJ::WStringToUTF8(wout);
+ size_t k = out.size();
+ while (k > 0 && out[--k] != '\\') {
+ }
+ out.resize(k);
+
+ out += "/../share/proj/";
+ out += name;
+
+ return NS_PROJ::FileManager::exists(ctx, out.c_str()) ? out.c_str()
+ : nullptr;
+}
+#endif
+
+/************************************************************************/
+/* pj_open_lib_internal() */
+/************************************************************************/
+
+#ifdef WIN32
+static const char dirSeparator = ';';
+#else
+static const char dirSeparator = ':';
+#endif
+
+static const char *proj_lib_name =
+#ifdef PROJ_LIB
+ PROJ_LIB;
+#else
+ nullptr;
+#endif
+
+static bool dontReadUserWritableDirectory() {
+ // Env var mostly for testing purposes and being independent from
+ // an existing installation
+ const char *envVar = getenv("PROJ_SKIP_READ_USER_WRITABLE_DIRECTORY");
+ return envVar != nullptr && envVar[0] != '\0';
+}
+
+static void *
+pj_open_lib_internal(projCtx ctx, const char *name, const char *mode,
+ void *(*open_file)(projCtx, const char *, const char *),
+ char *out_full_filename, size_t out_full_filename_size) {
+ try {
+ std::string fname;
+ const char *sysname = nullptr;
+ void *fid = nullptr;
+ std::string projLib;
+
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+
+ if (out_full_filename != nullptr && out_full_filename_size > 0)
+ out_full_filename[0] = '\0';
+
+ /* check if ~/name */
+ if (is_tilde_slash(name))
+ if ((sysname = getenv("HOME")) != nullptr) {
+ fname = sysname;
+ fname += DIR_CHAR;
+ fname += name;
+ sysname = fname.c_str();
+ } else
+ return nullptr;
+
+ /* or fixed path: /name, ./name or ../name */
+ else if (is_rel_or_absolute_filename(name)) {
+ sysname = name;
+#ifdef _WIN32
+ try {
+ NS_PROJ::UTF8ToWString(name);
+ } catch (const std::exception &) {
+ fname = NS_PROJ::Win32Recode(name, CP_ACP, CP_UTF8);
+ sysname = fname.c_str();
+ }
+#endif
+ }
+
+ else if (starts_with(name, "http://") || starts_with(name, "https://"))
+ sysname = name;
+
+ /* or try to use application provided file finder */
+ else if (ctx->file_finder != nullptr &&
+ (sysname = ctx->file_finder(
+ ctx, name, ctx->file_finder_user_data)) != nullptr)
+ ;
+
+ else if (ctx->file_finder_legacy != nullptr &&
+ (sysname = ctx->file_finder_legacy(name)) != nullptr)
+ ;
+
+ /* The user has search paths set */
+ else if (!ctx->search_paths.empty()) {
+ for (const auto &path : ctx->search_paths) {
+ try {
+ fname = path;
+ fname += DIR_CHAR;
+ fname += name;
+ sysname = fname.c_str();
+ fid = open_file(ctx, sysname, mode);
+ } catch (const std::exception &) {
+ }
+ if (fid)
+ break;
+ }
+ }
+
+ else if (!dontReadUserWritableDirectory() &&
+ (fid = open_file(
+ ctx, (pj_context_get_user_writable_directory(ctx, false) +
+ DIR_CHAR + name)
+ .c_str(),
+ mode)) != nullptr) {
+ fname = pj_context_get_user_writable_directory(ctx, false);
+ fname += DIR_CHAR;
+ fname += name;
+ sysname = fname.c_str();
+ }
+
+ /* if is environment PROJ_LIB defined */
+ else if (!(projLib = NS_PROJ::FileManager::getProjLibEnvVar(ctx))
+ .empty()) {
+ auto paths = NS_PROJ::internal::split(projLib, dirSeparator);
+ for (const auto &path : paths) {
+ fname = path;
+ fname += DIR_CHAR;
+ fname += name;
+ sysname = fname.c_str();
+ fid = open_file(ctx, sysname, mode);
+ if (fid)
+ break;
+ }
+#ifdef _WIN32
+ /* check if it lives in a ../share/proj dir of the proj dll */
+ } else if ((sysname = get_path_from_win32_projlib(ctx, name, fname)) !=
+ nullptr) {
+#endif
+ /* or hardcoded path */
+ } else if ((sysname = proj_lib_name) != nullptr) {
+ fname = sysname;
+ fname += DIR_CHAR;
+ fname += name;
+ sysname = fname.c_str();
+ /* just try it bare bones */
+ } else {
+ sysname = name;
+ }
+
+ assert(sysname); // to make Coverity Scan happy
+ if (fid != nullptr ||
+ (fid = open_file(ctx, sysname, mode)) != nullptr) {
+ if (out_full_filename != nullptr && out_full_filename_size > 0) {
+ // cppcheck-suppress nullPointer
+ strncpy(out_full_filename, sysname, out_full_filename_size);
+ out_full_filename[out_full_filename_size - 1] = '\0';
+ }
+ errno = 0;
+ }
+
+ if (ctx->last_errno == 0 && errno != 0)
+ pj_ctx_set_errno(ctx, errno);
+
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "pj_open_lib(%s): call fopen(%s) - %s",
+ name, sysname, fid == nullptr ? "failed" : "succeeded");
+
+ return (fid);
+ } catch (const std::exception &) {
+
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "pj_open_lib(%s): out of memory", name);
+
+ return nullptr;
+ }
+}
+
+/************************************************************************/
+/* pj_open_file_with_manager() */
+/************************************************************************/
+
+static void *pj_open_file_with_manager(projCtx ctx, const char *name,
+ const char * /* mode */) {
+ return NS_PROJ::FileManager::open(ctx, name, NS_PROJ::FileAccess::READ_ONLY)
+ .release();
+}
+
+/************************************************************************/
+/* FileManager::open_resource_file() */
+/************************************************************************/
+
+std::unique_ptr<NS_PROJ::File>
+NS_PROJ::FileManager::open_resource_file(projCtx ctx, const char *name) {
+
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+
+ auto file = std::unique_ptr<NS_PROJ::File>(
+ reinterpret_cast<NS_PROJ::File *>(pj_open_lib_internal(
+ ctx, name, "rb", pj_open_file_with_manager, nullptr, 0)));
+
+ // Retry with a .tif extension if the file name doesn't end with .tif
+ if (file == nullptr && !is_tilde_slash(name) &&
+ !is_rel_or_absolute_filename(name) && !starts_with(name, "http://") &&
+ !starts_with(name, "https://") && strcmp(name, "proj.db") != 0 &&
+ strstr(name, ".tif") == nullptr) {
+ std::string filename(name);
+ auto pos = filename.rfind('.');
+ if (pos + 4 == filename.size()) {
+ filename = filename.substr(0, pos) + ".tif";
+ file.reset(reinterpret_cast<NS_PROJ::File *>(
+ pj_open_lib_internal(ctx, filename.c_str(), "rb",
+ pj_open_file_with_manager, nullptr, 0)));
+ } else {
+ // For example for resource files like 'alaska'
+ filename += ".tif";
+ file.reset(reinterpret_cast<NS_PROJ::File *>(
+ pj_open_lib_internal(ctx, filename.c_str(), "rb",
+ pj_open_file_with_manager, nullptr, 0)));
+ }
+ if (file) {
+ pj_ctx_set_errno(ctx, 0);
+ }
+ }
+
+ if (file == nullptr && !is_tilde_slash(name) &&
+ !is_rel_or_absolute_filename(name) && !starts_with(name, "http://") &&
+ !starts_with(name, "https://") && pj_context_is_network_enabled(ctx)) {
+ std::string remote_file(pj_context_get_url_endpoint(ctx));
+ if (!remote_file.empty()) {
+ if (remote_file.back() != '/') {
+ remote_file += '/';
+ }
+ remote_file += name;
+ auto pos = remote_file.rfind('.');
+ if (pos + 4 == remote_file.size()) {
+ remote_file = remote_file.substr(0, pos) + ".tif";
+ file = open(ctx, remote_file.c_str(),
+ NS_PROJ::FileAccess::READ_ONLY);
+ if (file) {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Using %s",
+ remote_file.c_str());
+ pj_ctx_set_errno(ctx, 0);
+ }
+ } else {
+ // For example for resource files like 'alaska'
+ auto remote_file_tif = remote_file + ".tif";
+ file = open(ctx, remote_file_tif.c_str(),
+ NS_PROJ::FileAccess::READ_ONLY);
+ if (file) {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Using %s",
+ remote_file_tif.c_str());
+ pj_ctx_set_errno(ctx, 0);
+ } else {
+ // Init files
+ file = open(ctx, remote_file.c_str(),
+ NS_PROJ::FileAccess::READ_ONLY);
+ if (file) {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Using %s",
+ remote_file.c_str());
+ pj_ctx_set_errno(ctx, 0);
+ }
+ }
+ }
+ }
+ }
+ return file;
+}
+
+/************************************************************************/
+/* pj_open_lib() */
+/************************************************************************/
+
+#ifndef REMOVE_LEGACY_SUPPORT
+
+// Used by following legacy function
+static void *pj_ctx_fopen_adapter(projCtx ctx, const char *name,
+ const char *mode) {
+ return pj_ctx_fopen(ctx, name, mode);
+}
+
+// Legacy function
+PAFile pj_open_lib(projCtx ctx, const char *name, const char *mode) {
+ return (PAFile)pj_open_lib_internal(ctx, name, mode, pj_ctx_fopen_adapter,
+ nullptr, 0);
+}
+
+#endif // REMOVE_LEGACY_SUPPORT
+
+/************************************************************************/
+/* pj_find_file() */
+/************************************************************************/
+
+/** Returns the full filename corresponding to a proj resource file specified
+ * as a short filename.
+ *
+ * @param ctx context.
+ * @param short_filename short filename (e.g. egm96_15.gtx). Must not be NULL.
+ * @param out_full_filename output buffer, of size out_full_filename_size, that
+ * will receive the full filename on success.
+ * Will be zero-terminated.
+ * @param out_full_filename_size size of out_full_filename.
+ * @return 1 if the file was found, 0 otherwise.
+ */
+int pj_find_file(projCtx ctx, const char *short_filename,
+ char *out_full_filename, size_t out_full_filename_size) {
+ auto f = reinterpret_cast<NS_PROJ::File *>(pj_open_lib_internal(
+ ctx, short_filename, "rb", pj_open_file_with_manager, out_full_filename,
+ out_full_filename_size));
+ if (f != nullptr) {
+ delete f;
+ return 1;
+ }
+ return 0;
+}
+
+/************************************************************************/
+/* pj_context_get_url_endpoint() */
+/************************************************************************/
+
+std::string pj_context_get_url_endpoint(PJ_CONTEXT *ctx) {
+ if (!ctx->endpoint.empty()) {
+ return ctx->endpoint;
+ }
+ pj_load_ini(ctx);
+ return ctx->endpoint;
+}
+
+/************************************************************************/
+/* trim() */
+/************************************************************************/
+
+static std::string trim(const std::string &s) {
+ const auto first = s.find_first_not_of(' ');
+ const auto last = s.find_last_not_of(' ');
+ if (first == std::string::npos || last == std::string::npos) {
+ return std::string();
+ }
+ return s.substr(first, last - first + 1);
+}
+
+/************************************************************************/
+/* pj_load_ini() */
+/************************************************************************/
+
+void pj_load_ini(projCtx ctx) {
+ if (ctx->iniFileLoaded)
+ return;
+
+ const char *endpoint_from_env = getenv("PROJ_NETWORK_ENDPOINT");
+ if (endpoint_from_env && endpoint_from_env[0] != '\0') {
+ ctx->endpoint = endpoint_from_env;
+ }
+
+ ctx->iniFileLoaded = true;
+ auto file = std::unique_ptr<NS_PROJ::File>(
+ reinterpret_cast<NS_PROJ::File *>(pj_open_lib_internal(
+ ctx, "proj.ini", "rb", pj_open_file_with_manager, nullptr, 0)));
+ if (!file)
+ return;
+ file->seek(0, SEEK_END);
+ const auto filesize = file->tell();
+ if (filesize == 0 || filesize > 100 * 1024U)
+ return;
+ file->seek(0, SEEK_SET);
+ std::string content;
+ content.resize(static_cast<size_t>(filesize));
+ const auto nread = file->read(&content[0], content.size());
+ if (nread != content.size())
+ return;
+ content += '\n';
+ size_t pos = 0;
+ while (pos != std::string::npos) {
+ const auto eol = content.find_first_of("\r\n", pos);
+ if (eol == std::string::npos) {
+ break;
+ }
+
+ const auto equal = content.find('=', pos);
+ if (equal < eol) {
+ const auto key = trim(content.substr(pos, equal - pos));
+ const auto value =
+ trim(content.substr(equal + 1, eol - (equal + 1)));
+ if (ctx->endpoint.empty() && key == "cdn_endpoint") {
+ ctx->endpoint = value;
+ } else if (key == "network") {
+ const char *enabled = getenv("PROJ_NETWORK");
+ if (enabled == nullptr || enabled[0] == '\0') {
+ ctx->networking.enabled = ci_equal(value, "ON") ||
+ ci_equal(value, "YES") ||
+ ci_equal(value, "TRUE");
+ }
+ } else if (key == "cache_enabled") {
+ ctx->gridChunkCache.enabled = ci_equal(value, "ON") ||
+ ci_equal(value, "YES") ||
+ ci_equal(value, "TRUE");
+ } else if (key == "cache_size_MB") {
+ const int val = atoi(value.c_str());
+ ctx->gridChunkCache.max_size =
+ val > 0 ? static_cast<long long>(val) * 1024 * 1024 : -1;
+ } else if (key == "cache_ttl_sec") {
+ ctx->gridChunkCache.ttl = atoi(value.c_str());
+ }
+ }
+
+ pos = content.find_first_not_of("\r\n", eol);
+ }
+}
+
+//! @endcond
+
+/************************************************************************/
+/* pj_set_finder() */
+/************************************************************************/
+
+void pj_set_finder(const char *(*new_finder)(const char *))
+
+{
+ auto ctx = pj_get_default_ctx();
+ if (ctx) {
+ ctx->file_finder_legacy = new_finder;
+ }
+}
+
+/************************************************************************/
+/* proj_context_set_file_finder() */
+/************************************************************************/
+
+/** \brief Assign a file finder callback to a context.
+ *
+ * This callback will be used whenever PROJ must open one of its resource files
+ * (proj.db database, grids, etc...)
+ *
+ * The callback will be called with the context currently in use at the moment
+ * where it is used (not necessarily the one provided during this call), and
+ * with the provided user_data (which may be NULL).
+ * The user_data must remain valid during the whole lifetime of the context.
+ *
+ * A finder set on the default context will be inherited by contexts created
+ * later.
+ *
+ * @param ctx PROJ context, or NULL for the default context.
+ * @param finder Finder callback. May be NULL
+ * @param user_data User data provided to the finder callback. May be NULL.
+ *
+ * @since PROJ 6.0
+ */
+void proj_context_set_file_finder(PJ_CONTEXT *ctx, proj_file_finder finder,
+ void *user_data) {
+ if (!ctx)
+ ctx = pj_get_default_ctx();
+ if (!ctx)
+ return;
+ ctx->file_finder = finder;
+ ctx->file_finder_user_data = user_data;
+}
+
+/************************************************************************/
+/* proj_context_set_search_paths() */
+/************************************************************************/
+
+/** \brief Sets search paths.
+ *
+ * Those search paths will be used whenever PROJ must open one of its resource
+ * files
+ * (proj.db database, grids, etc...)
+ *
+ * If set on the default context, they will be inherited by contexts created
+ * later.
+ *
+ * Starting with PROJ 7.0, the path(s) should be encoded in UTF-8.
+ *
+ * @param ctx PROJ context, or NULL for the default context.
+ * @param count_paths Number of paths. 0 if paths == NULL.
+ * @param paths Paths. May be NULL.
+ *
+ * @since PROJ 6.0
+ */
+void proj_context_set_search_paths(PJ_CONTEXT *ctx, int count_paths,
+ const char *const *paths) {
+ if (!ctx)
+ ctx = pj_get_default_ctx();
+ if (!ctx)
+ return;
+ try {
+ std::vector<std::string> vector_of_paths;
+ for (int i = 0; i < count_paths; i++) {
+ vector_of_paths.emplace_back(paths[i]);
+ }
+ ctx->set_search_paths(vector_of_paths);
+ } catch (const std::exception &) {
+ }
+}
+
+/************************************************************************/
+/* pj_set_searchpath() */
+/* */
+/* Path control for callers that can't practically provide */
+/* pj_set_finder() style callbacks. Call with (0,NULL) as args */
+/* to clear the searchpath set. */
+/************************************************************************/
+
+void pj_set_searchpath(int count, const char **path) {
+ proj_context_set_search_paths(nullptr, count,
+ const_cast<const char *const *>(path));
+}
diff --git a/src/filemanager.hpp b/src/filemanager.hpp
new file mode 100644
index 00000000..554bd325
--- /dev/null
+++ b/src/filemanager.hpp
@@ -0,0 +1,100 @@
+/******************************************************************************
+ * Project: PROJ
+ * Purpose: File manager
+ * Author: Even Rouault, <even.rouault at spatialys.com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2019, Even Rouault, <even.rouault at spatialys.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *****************************************************************************/
+
+#ifndef FILEMANAGER_HPP_INCLUDED
+#define FILEMANAGER_HPP_INCLUDED
+
+#include <memory>
+
+#include "proj.h"
+#include "proj/util.hpp"
+
+//! @cond Doxygen_Suppress
+
+NS_PROJ_START
+
+class File;
+
+enum class FileAccess {
+ READ_ONLY, // "rb"
+ READ_UPDATE, // "r+b"
+ CREATE, // "w+b"
+};
+
+class FileManager {
+ private:
+ FileManager() = delete;
+
+ public:
+ // "Low-level" interface.
+ static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename,
+ FileAccess access);
+ static bool exists(PJ_CONTEXT *ctx, const char *filename);
+ static bool mkdir(PJ_CONTEXT *ctx, const char *filename);
+ static bool unlink(PJ_CONTEXT *ctx, const char *filename);
+ static bool rename(PJ_CONTEXT *ctx, const char *oldPath,
+ const char *newPath);
+ static std::string getProjLibEnvVar(PJ_CONTEXT *ctx);
+
+ // "High-level" interface, honoring PROJ_LIB and the like.
+ static std::unique_ptr<File> open_resource_file(PJ_CONTEXT *ctx,
+ const char *name);
+
+ static void fillDefaultNetworkInterface(PJ_CONTEXT *ctx);
+
+ static void clearMemoryCache();
+};
+
+// ---------------------------------------------------------------------------
+
+class File {
+ protected:
+ std::string name_;
+ explicit File(const std::string &name);
+
+ public:
+ virtual ~File();
+ virtual size_t read(void *buffer, size_t sizeBytes) = 0;
+ virtual size_t write(const void *buffer, size_t sizeBytes) = 0;
+ virtual bool seek(unsigned long long offset, int whence = SEEK_SET) = 0;
+ virtual unsigned long long tell() = 0;
+ virtual void reassign_context(PJ_CONTEXT *ctx) = 0;
+ virtual bool hasChanged() const = 0;
+
+ const std::string &name() const { return name_; }
+};
+
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<File> pj_network_file_open(PJ_CONTEXT *ctx,
+ const char *filename);
+
+NS_PROJ_END
+
+//! @endcond Doxygen_Suppress
+
+#endif // FILEMANAGER_HPP_INCLUDED
diff --git a/src/gc_reader.cpp b/src/gc_reader.cpp
deleted file mode 100644
index def52a11..00000000
--- a/src/gc_reader.cpp
+++ /dev/null
@@ -1,248 +0,0 @@
-/******************************************************************************
- * Project: PROJ.4
- * Purpose: Code to read a grid catalog from a .cvs file.
- * Author: Frank Warmerdam, warmerdam@pobox.com
- *
- ******************************************************************************
- * Copyright (c) 2012, Frank Warmerdam <warmerdam@pobox.com>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included
- * in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- *****************************************************************************/
-
-#define PJ_LIB__
-
-#include <ctype.h>
-#include <errno.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "proj.h"
-#include "proj_internal.h"
-
-static int gc_readentry(projCtx ctx, PAFile fid, PJ_GridCatalogEntry *entry);
-
-/************************************************************************/
-/* pj_gc_readcatalog() */
-/* */
-/* Read a grid catalog from a .csv file. */
-/************************************************************************/
-
-PJ_GridCatalog *pj_gc_readcatalog( projCtx ctx, const char *catalog_name )
-{
- PAFile fid;
- PJ_GridCatalog *catalog;
- int entry_max;
- char line[302];
-
- fid = pj_open_lib( ctx, catalog_name, "r" );
- if (fid == nullptr)
- return nullptr;
-
- /* discard title line */
- pj_ctx_fgets(ctx, line, sizeof(line)-1, fid);
-
- catalog = (PJ_GridCatalog *) calloc(1,sizeof(PJ_GridCatalog));
- if( !catalog )
- {
- pj_ctx_set_errno(ctx, ENOMEM);
- pj_ctx_fclose(ctx, fid);
- return nullptr;
- }
-
- catalog->catalog_name = pj_strdup(catalog_name);
- if (!catalog->catalog_name) {
- pj_ctx_set_errno(ctx, ENOMEM);
- free(catalog);
- pj_ctx_fclose(ctx, fid);
- return nullptr;
- }
-
- entry_max = 10;
- catalog->entries = (PJ_GridCatalogEntry *)
- malloc(entry_max * sizeof(PJ_GridCatalogEntry));
- if (!catalog->entries) {
- pj_ctx_set_errno(ctx, ENOMEM);
- free(catalog->catalog_name);
- free(catalog);
- pj_ctx_fclose(ctx, fid);
- return nullptr;
- }
-
- while( gc_readentry( ctx, fid,
- catalog->entries+catalog->entry_count) == 0)
- {
- catalog->entry_count++;
-
- if( catalog->entry_count == entry_max )
- {
- PJ_GridCatalogEntry* new_entries;
- entry_max = entry_max * 2;
- new_entries = (PJ_GridCatalogEntry *)
- realloc(catalog->entries,
- entry_max * sizeof(PJ_GridCatalogEntry));
- if (new_entries == nullptr )
- {
- int i;
- for( i = 0; i < catalog->entry_count; i++ )
- free( catalog->entries[i].definition );
- free( catalog->entries );
- free( catalog->catalog_name );
- free( catalog );
- pj_ctx_fclose(ctx, fid);
- return nullptr;
- }
- catalog->entries = new_entries;
- }
- }
-
- pj_ctx_fclose(ctx, fid);
-
- return catalog;
-}
-
-/************************************************************************/
-/* gc_read_csv_line() */
-/* */
-/* Simple csv line splitter with fixed maximum line size and */
-/* token count. */
-/************************************************************************/
-
-static int gc_read_csv_line( projCtx ctx, PAFile fid,
- char **tokens, int max_tokens )
-{
- char line[302];
-
- while( pj_ctx_fgets(ctx, line, sizeof(line)-1, fid) != nullptr )
- {
- char *next = line;
- int token_count = 0;
-
- while( isspace(*next) )
- next++;
-
- /* skip blank and comment lines */
- if( next[0] == '#' || next[0] == '\0' )
- continue;
-
- while( token_count < max_tokens && *next != '\0' )
- {
- const char *start = next;
- char* token;
-
- while( *next != '\0' && *next != ',' )
- next++;
-
- if( *next == ',' )
- {
- *next = '\0';
- next++;
- }
-
- token = pj_strdup(start);
- if (!token) {
- while (token_count > 0)
- free(tokens[--token_count]);
- pj_ctx_set_errno(ctx, ENOMEM);
- return 0;
- }
- tokens[token_count++] = token;
- }
-
- return token_count;
- }
-
- return 0;
-}
-
-/************************************************************************/
-/* pj_gc_parsedate() */
-/* */
-/* Parse a date into a floating point year value. Acceptable */
-/* values are "yyyy.fraction" and "yyyy-mm-dd". Anything else */
-/* returns 0.0. */
-/************************************************************************/
-
-double pj_gc_parsedate( projCtx ctx, const char *date_string )
-{
- (void) ctx;
-
- if( strlen(date_string) == 10
- && date_string[4] == '-' && date_string[7] == '-' )
- {
- int year = atoi(date_string);
- int month = atoi(date_string+5);
- int day = atoi(date_string+8);
-
- /* simplified calculation so we don't need to know all about months */
- return year + ((month-1) * 31 + (day-1)) / 372.0;
- }
- else
- {
- return pj_atof(date_string);
- }
-}
-
-
-/************************************************************************/
-/* gc_readentry() */
-/* */
-/* Read one catalog entry from the file */
-/* */
-/* Format: */
-/* gridname,ll_long,ll_lat,ur_long,ur_lat,priority,date */
-/************************************************************************/
-
-static int gc_readentry(projCtx ctx, PAFile fid, PJ_GridCatalogEntry *entry)
-{
-#define MAX_TOKENS 30
- char *tokens[MAX_TOKENS];
- int token_count, i;
- int error = 0;
-
- memset( entry, 0, sizeof(PJ_GridCatalogEntry) );
-
- token_count = gc_read_csv_line( ctx, fid, tokens, MAX_TOKENS );
- if( token_count < 5 )
- {
- error = 1; /* TODO: need real error codes */
- if( token_count != 0 )
- pj_log( ctx, PJ_LOG_ERROR, "Short line in grid catalog." );
- }
- else
- {
- entry->definition = tokens[0];
- tokens[0] = nullptr; /* We take ownership of tokens[0] */
- entry->region.ll_long = dmstor_ctx( ctx, tokens[1], nullptr );
- entry->region.ll_lat = dmstor_ctx( ctx, tokens[2], nullptr );
- entry->region.ur_long = dmstor_ctx( ctx, tokens[3], nullptr );
- entry->region.ur_lat = dmstor_ctx( ctx, tokens[4], nullptr );
- if( token_count > 5 )
- entry->priority = atoi( tokens[5] ); /* defaults to zero */
- if( token_count > 6 )
- entry->date = pj_gc_parsedate( ctx, tokens[6] );
- }
-
- for( i = 0; i < token_count; i++ )
- free( tokens[i] );
-
- return error;
-}
-
-
-
diff --git a/src/gridcatalog.cpp b/src/gridcatalog.cpp
deleted file mode 100644
index 9b94fef8..00000000
--- a/src/gridcatalog.cpp
+++ /dev/null
@@ -1,306 +0,0 @@
-/******************************************************************************
- * Project: PROJ.4
- * Purpose: Code in support of grid catalogs
- * Author: Frank Warmerdam, warmerdam@pobox.com
- *
- ******************************************************************************
- * Copyright (c) 2012, Frank Warmerdam <warmerdam@pobox.com>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included
- * in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- *****************************************************************************/
-
-#define PJ_LIB__
-
-#include <assert.h>
-#include <math.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "proj.h"
-#include "proj_internal.h"
-
-static PJ_GridCatalog *grid_catalog_list = nullptr;
-
-static
-PJ_GRIDINFO *pj_gc_findgrid( projCtx_t *ctx,
- PJ_GridCatalog *catalog, int after,
- PJ_LP location, double date,
- PJ_Region *optional_region,
- double *grid_date );
-
-/************************************************************************/
-/* pj_gc_unloadall() */
-/* */
-/* Deallocate all the grid catalogs (but not the referenced */
-/* grids). */
-/************************************************************************/
-
-void pj_gc_unloadall( projCtx ctx )
-{
- (void) ctx;
-
- while( grid_catalog_list != nullptr )
- {
- int i;
- PJ_GridCatalog *catalog = grid_catalog_list;
- grid_catalog_list = grid_catalog_list->next;
-
- for( i = 0; i < catalog->entry_count; i++ )
- {
- /* we don't own gridinfo - do not free here */
- free( catalog->entries[i].definition );
- }
- free( catalog->entries );
- free( catalog->catalog_name );
- free( catalog );
- }
-}
-
-/************************************************************************/
-/* pj_gc_findcatalog() */
-/************************************************************************/
-
-PJ_GridCatalog *pj_gc_findcatalog( projCtx ctx, const char *name )
-
-{
- PJ_GridCatalog *catalog;
-
- pj_acquire_lock();
-
- for( catalog=grid_catalog_list; catalog != nullptr; catalog = catalog->next )
- {
- if( strcmp(catalog->catalog_name, name) == 0 )
- {
- pj_release_lock();
- return catalog;
- }
- }
-
- pj_release_lock();
-
- catalog = pj_gc_readcatalog( ctx, name );
- if( catalog == nullptr )
- return nullptr;
-
- pj_acquire_lock();
- catalog->next = grid_catalog_list;
- grid_catalog_list = catalog;
- pj_release_lock();
-
- return catalog;
-}
-
-/************************************************************************/
-/* pj_gc_apply_gridshift() */
-/************************************************************************/
-
-int pj_gc_apply_gridshift( PJ *defn, int inverse,
- long point_count, int point_offset,
- double *x, double *y, double *z )
-
-{
- int i;
- (void) z;
-
- if( defn->catalog == nullptr )
- {
- defn->catalog = pj_gc_findcatalog( defn->ctx, defn->catalog_name );
- if( defn->catalog == nullptr )
- return defn->ctx->last_errno;
- }
-
- defn->ctx->last_errno = 0;
-
- for( i = 0; i < point_count; i++ )
- {
- long io = i * point_offset;
- PJ_LP input, output_after, output_before;
- double mix_ratio;
- PJ_GRIDINFO *gi;
-
- input.phi = y[io];
- input.lam = x[io];
-
- /* make sure we have appropriate "after" shift file available */
- if( defn->last_after_grid == nullptr
- || input.lam < defn->last_after_region.ll_long
- || input.lam > defn->last_after_region.ur_long
- || input.phi < defn->last_after_region.ll_lat
- || input.phi > defn->last_after_region.ll_lat ) {
- defn->last_after_grid =
- pj_gc_findgrid( defn->ctx, defn->catalog,
- 1, input, defn->datum_date,
- &(defn->last_after_region),
- &(defn->last_after_date));
- if( defn->last_after_grid == nullptr )
- {
- pj_ctx_set_errno( defn->ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- return PJD_ERR_FAILED_TO_LOAD_GRID;
- }
- }
- gi = defn->last_after_grid;
- assert( gi->child == nullptr );
-
- /* load the grid shift info if we don't have it. */
- if( gi->ct->cvs == nullptr && !pj_gridinfo_load( defn->ctx, gi ) )
- {
- pj_ctx_set_errno( defn->ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- return PJD_ERR_FAILED_TO_LOAD_GRID;
- }
-
- output_after = nad_cvt( defn->ctx, input, inverse, gi->ct, 0, nullptr );
- if( output_after.lam == HUGE_VAL )
- {
- if( defn->ctx->debug_level >= PJ_LOG_DEBUG_MAJOR )
- {
- pj_log( defn->ctx, PJ_LOG_DEBUG_MAJOR,
- "pj_apply_gridshift(): failed to find a grid shift table for\n"
- " location (%.7fdW,%.7fdN)",
- x[io] * RAD_TO_DEG,
- y[io] * RAD_TO_DEG );
- }
- continue;
- }
-
- if( defn->datum_date == 0.0 )
- {
- y[io] = output_after.phi;
- x[io] = output_after.lam;
- continue;
- }
-
- /* make sure we have appropriate "before" shift file available */
- if( defn->last_before_grid == nullptr
- || input.lam < defn->last_before_region.ll_long
- || input.lam > defn->last_before_region.ur_long
- || input.phi < defn->last_before_region.ll_lat
- || input.phi > defn->last_before_region.ll_lat ) {
- defn->last_before_grid =
- pj_gc_findgrid( defn->ctx, defn->catalog,
- 0, input, defn->datum_date,
- &(defn->last_before_region),
- &(defn->last_before_date));
- if( defn->last_before_grid == nullptr )
- {
- pj_ctx_set_errno( defn->ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- return PJD_ERR_FAILED_TO_LOAD_GRID;
- }
- }
-
- gi = defn->last_before_grid;
- assert( gi->child == nullptr );
-
- /* load the grid shift info if we don't have it. */
- if( gi->ct->cvs == nullptr && !pj_gridinfo_load( defn->ctx, gi ) )
- {
- pj_ctx_set_errno( defn->ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- return PJD_ERR_FAILED_TO_LOAD_GRID;
- }
-
- output_before = nad_cvt( defn->ctx, input, inverse, gi->ct, 0, nullptr );
- if( output_before.lam == HUGE_VAL )
- {
- if( defn->ctx->debug_level >= PJ_LOG_DEBUG_MAJOR )
- {
- pj_log( defn->ctx, PJ_LOG_DEBUG_MAJOR,
- "pj_apply_gridshift(): failed to find a grid shift table for\n"
- " location (%.7fdW,%.7fdN)",
- x[io] * RAD_TO_DEG,
- y[io] * RAD_TO_DEG );
- }
- continue;
- }
-
- mix_ratio = (defn->datum_date - defn->last_before_date)
- / (defn->last_after_date - defn->last_before_date);
-
- y[io] = mix_ratio * output_after.phi
- + (1.0-mix_ratio) * output_before.phi;
- x[io] = mix_ratio * output_after.lam
- + (1.0-mix_ratio) * output_before.lam;
- }
-
- return 0;
-}
-
-/************************************************************************/
-/* pj_c_findgrid() */
-/************************************************************************/
-
-static
-PJ_GRIDINFO *pj_gc_findgrid( projCtx ctx, PJ_GridCatalog *catalog, int after,
- PJ_LP location, double date,
- PJ_Region *optional_region,
- double *grid_date )
-{
- int iEntry;
- PJ_GridCatalogEntry *entry = nullptr;
-
- for( iEntry = 0; iEntry < catalog->entry_count; iEntry++ )
- {
- entry = catalog->entries + iEntry;
-
- if( (after && entry->date < date)
- || (!after && entry->date > date) )
- continue;
-
- if( location.lam < entry->region.ll_long
- || location.lam > entry->region.ur_long
- || location.phi < entry->region.ll_lat
- || location.phi > entry->region.ur_lat )
- continue;
-
- if( entry->available == -1 )
- continue;
-
- break;
- }
-
- if( entry == nullptr )
- {
- if( grid_date )
- *grid_date = 0.0;
- if( optional_region != nullptr )
- memset( optional_region, 0, sizeof(PJ_Region));
- return nullptr;
- }
-
- if( grid_date )
- *grid_date = entry->date;
-
- if( optional_region )
- {
-
- }
-
- if( entry->gridinfo == nullptr )
- {
- PJ_GRIDINFO **gridlist = nullptr;
- int grid_count = 0;
- gridlist = pj_gridlist_from_nadgrids( ctx, entry->definition,
- &grid_count);
- // FIXME: this leaks gridlist itself, and memory ownership of
- // entry->gridinfo is also confusing. Coverity CID 193539
- if( grid_count == 1 )
- entry->gridinfo = gridlist[0];
- }
-
- return entry->gridinfo;
-}
-
diff --git a/src/gridinfo.cpp b/src/gridinfo.cpp
deleted file mode 100644
index 7f7d930f..00000000
--- a/src/gridinfo.cpp
+++ /dev/null
@@ -1,988 +0,0 @@
-/******************************************************************************
- * Project: PROJ.4
- * Purpose: Functions for handling individual PJ_GRIDINFO's. Includes
- * loaders for all formats but CTABLE (in nad_init.c).
- * Author: Frank Warmerdam, warmerdam@pobox.com
- *
- ******************************************************************************
- * Copyright (c) 2000, Frank Warmerdam <warmerdam@pobox.com>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included
- * in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- *****************************************************************************/
-
-#define PJ_LIB__
-
-#include <errno.h>
-#include <math.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "proj_internal.h"
-
-/************************************************************************/
-/* swap_words() */
-/* */
-/* Convert the byte order of the given word(s) in place. */
-/************************************************************************/
-
-static const int byte_order_test = 1;
-#define IS_LSB (1 == ((const unsigned char *) (&byte_order_test))[0])
-
-static void swap_words( unsigned char *data, int word_size, int word_count )
-
-{
- int word;
-
- for( word = 0; word < word_count; word++ )
- {
- int i;
-
- for( i = 0; i < word_size/2; i++ )
- {
- unsigned char t;
-
- t = data[i];
- data[i] = data[word_size-i-1];
- data[word_size-i-1] = t;
- }
-
- data += word_size;
- }
-}
-
-/************************************************************************/
-/* to_double() */
-/* */
-/* Returns a double from the pointed data. */
-/************************************************************************/
-
-static double to_double( unsigned char* data )
-{
- double d;
- memcpy(&d, data, sizeof(d));
- return d;
-}
-
-/************************************************************************/
-/* pj_gridinfo_free() */
-/************************************************************************/
-
-void pj_gridinfo_free( projCtx ctx, PJ_GRIDINFO *gi )
-
-{
- if( gi == nullptr )
- return;
-
- if( gi->child != nullptr )
- {
- PJ_GRIDINFO *child, *next;
-
- for( child = gi->child; child != nullptr; child=next)
- {
- next=child->next;
- pj_gridinfo_free( ctx, child );
- }
- }
-
- if( gi->ct != nullptr )
- nad_free( gi->ct );
-
- free( gi->gridname );
- if( gi->filename != nullptr )
- free( gi->filename );
-
- pj_dalloc( gi );
-}
-
-/************************************************************************/
-/* pj_gridinfo_load() */
-/* */
-/* This function is intended to implement delayed loading of */
-/* the data contents of a grid file. The header and related */
-/* stuff are loaded by pj_gridinfo_init(). */
-/************************************************************************/
-
-int pj_gridinfo_load( projCtx_t* ctx, PJ_GRIDINFO *gi )
-
-{
- struct CTABLE ct_tmp;
-
- if( gi == nullptr || gi->ct == nullptr )
- return 0;
-
- pj_acquire_lock();
- if( gi->ct->cvs != nullptr )
- {
- pj_release_lock();
- return 1;
- }
-
- memcpy(&ct_tmp, gi->ct, sizeof(struct CTABLE));
-
-/* -------------------------------------------------------------------- */
-/* Original platform specific CTable format. */
-/* -------------------------------------------------------------------- */
- if( strcmp(gi->format,"ctable") == 0 )
- {
- PAFile fid;
- int result;
-
- fid = pj_open_lib( ctx, gi->filename, "rb" );
-
- if( fid == nullptr )
- {
- pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- pj_release_lock();
- return 0;
- }
-
- result = nad_ctable_load( ctx, &ct_tmp, (struct projFileAPI_t*)fid );
-
- pj_ctx_fclose( ctx, fid );
-
- gi->ct->cvs = ct_tmp.cvs;
- pj_release_lock();
-
- return result;
- }
-
-/* -------------------------------------------------------------------- */
-/* CTable2 format. */
-/* -------------------------------------------------------------------- */
- else if( strcmp(gi->format,"ctable2") == 0 )
- {
- PAFile fid;
- int result;
-
- fid = pj_open_lib( ctx, gi->filename, "rb" );
-
- if( fid == nullptr )
- {
- pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- pj_release_lock();
- return 0;
- }
-
- result = nad_ctable2_load( ctx, &ct_tmp, (struct projFileAPI_t*)fid );
-
- pj_ctx_fclose( ctx, fid );
-
- gi->ct->cvs = ct_tmp.cvs;
-
- pj_release_lock();
- return result;
- }
-
-/* -------------------------------------------------------------------- */
-/* NTv1 format. */
-/* We process one line at a time. Note that the array storage */
-/* direction (e-w) is different in the NTv1 file and what */
-/* the CTABLE is supposed to have. The phi/lam are also */
-/* reversed, and we have to be aware of byte swapping. */
-/* -------------------------------------------------------------------- */
- else if( strcmp(gi->format,"ntv1") == 0 )
- {
- double *row_buf;
- int row;
- PAFile fid;
-
- fid = pj_open_lib( ctx, gi->filename, "rb" );
-
- if( fid == nullptr )
- {
- pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- pj_release_lock();
- return 0;
- }
-
- pj_ctx_fseek( ctx, fid, gi->grid_offset, SEEK_SET );
-
- row_buf = (double *) pj_malloc(gi->ct->lim.lam * sizeof(double) * 2);
- ct_tmp.cvs = (FLP *) pj_malloc(gi->ct->lim.lam*gi->ct->lim.phi*sizeof(FLP));
- if( row_buf == nullptr || ct_tmp.cvs == nullptr )
- {
- pj_dalloc( row_buf );
- pj_dalloc( ct_tmp.cvs );
- pj_ctx_set_errno( ctx, ENOMEM );
- pj_release_lock();
- return 0;
- }
-
- for( row = 0; row < gi->ct->lim.phi; row++ )
- {
- int i;
- FLP *cvs;
- double *diff_seconds;
-
- if( pj_ctx_fread( ctx, row_buf,
- sizeof(double), gi->ct->lim.lam * 2, fid )
- != (size_t)( 2 * gi->ct->lim.lam ) )
- {
- pj_dalloc( row_buf );
- pj_dalloc( ct_tmp.cvs );
- pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- pj_release_lock();
- return 0;
- }
-
- if( IS_LSB )
- swap_words( (unsigned char *) row_buf, 8, gi->ct->lim.lam*2 );
-
- /* convert seconds to radians */
- diff_seconds = row_buf;
-
- for( i = 0; i < gi->ct->lim.lam; i++ )
- {
- cvs = ct_tmp.cvs + (row) * gi->ct->lim.lam
- + (gi->ct->lim.lam - i - 1);
-
- cvs->phi = (float) (*(diff_seconds++) * ((M_PI/180.0) / 3600.0));
- cvs->lam = (float) (*(diff_seconds++) * ((M_PI/180.0) / 3600.0));
- }
- }
-
- pj_dalloc( row_buf );
-
- pj_ctx_fclose( ctx, fid );
-
- gi->ct->cvs = ct_tmp.cvs;
- pj_release_lock();
-
- return 1;
- }
-
-/* -------------------------------------------------------------------- */
-/* NTv2 format. */
-/* We process one line at a time. Note that the array storage */
-/* direction (e-w) is different in the NTv2 file and what */
-/* the CTABLE is supposed to have. The phi/lam are also */
-/* reversed, and we have to be aware of byte swapping. */
-/* -------------------------------------------------------------------- */
- else if( strcmp(gi->format,"ntv2") == 0 )
- {
- float *row_buf;
- int row;
- PAFile fid;
-
- pj_log( ctx, PJ_LOG_DEBUG_MINOR,
- "NTv2 - loading grid %s", gi->ct->id );
-
- fid = pj_open_lib( ctx, gi->filename, "rb" );
-
- if( fid == nullptr )
- {
- pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- pj_release_lock();
- return 0;
- }
-
- pj_ctx_fseek( ctx, fid, gi->grid_offset, SEEK_SET );
-
- row_buf = (float *) pj_malloc(gi->ct->lim.lam * sizeof(float) * 4);
- ct_tmp.cvs = (FLP *) pj_malloc(gi->ct->lim.lam*gi->ct->lim.phi*sizeof(FLP));
- if( row_buf == nullptr || ct_tmp.cvs == nullptr )
- {
- pj_dalloc( row_buf );
- pj_dalloc( ct_tmp.cvs );
- pj_ctx_set_errno( ctx, ENOMEM );
- pj_release_lock();
- return 0;
- }
-
- for( row = 0; row < gi->ct->lim.phi; row++ )
- {
- int i;
- FLP *cvs;
- float *diff_seconds;
-
- if( pj_ctx_fread( ctx, row_buf, sizeof(float),
- gi->ct->lim.lam*4, fid )
- != (size_t)( 4 * gi->ct->lim.lam ) )
- {
- pj_dalloc( row_buf );
- pj_dalloc( ct_tmp.cvs );
- pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- pj_release_lock();
- return 0;
- }
-
- if( gi->must_swap )
- swap_words( (unsigned char *) row_buf, 4,
- gi->ct->lim.lam*4 );
-
- /* convert seconds to radians */
- diff_seconds = row_buf;
-
- for( i = 0; i < gi->ct->lim.lam; i++ )
- {
- cvs = ct_tmp.cvs + (row) * gi->ct->lim.lam
- + (gi->ct->lim.lam - i - 1);
-
- cvs->phi = (float) (*(diff_seconds++) * ((M_PI/180.0) / 3600.0));
- cvs->lam = (float) (*(diff_seconds++) * ((M_PI/180.0) / 3600.0));
- diff_seconds += 2; /* skip accuracy values */
- }
- }
-
- pj_dalloc( row_buf );
-
- pj_ctx_fclose( ctx, fid );
-
- gi->ct->cvs = ct_tmp.cvs;
-
- pj_release_lock();
- return 1;
- }
-
-/* -------------------------------------------------------------------- */
-/* GTX format. */
-/* -------------------------------------------------------------------- */
- else if( strcmp(gi->format,"gtx") == 0 )
- {
- int words = gi->ct->lim.lam * gi->ct->lim.phi;
- PAFile fid;
-
- fid = pj_open_lib( ctx, gi->filename, "rb" );
-
- if( fid == nullptr )
- {
- pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- pj_release_lock();
- return 0;
- }
-
- pj_ctx_fseek( ctx, fid, gi->grid_offset, SEEK_SET );
-
- ct_tmp.cvs = (FLP *) pj_malloc(words*sizeof(float));
- if( ct_tmp.cvs == nullptr )
- {
- pj_ctx_set_errno( ctx, ENOMEM );
- pj_release_lock();
- return 0;
- }
-
- if( pj_ctx_fread( ctx, ct_tmp.cvs, sizeof(float), words, fid )
- != (size_t)words )
- {
- pj_dalloc( ct_tmp.cvs );
- pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- pj_release_lock();
- return 0;
- }
-
- if( IS_LSB )
- swap_words( (unsigned char *) ct_tmp.cvs, 4, words );
-
- pj_ctx_fclose( ctx, fid );
- gi->ct->cvs = ct_tmp.cvs;
- pj_release_lock();
- return 1;
- }
-
- else
- {
- pj_release_lock();
- return 0;
- }
-}
-
-/************************************************************************/
-/* gridinfo_parent() */
-/* */
-/* Seek a parent grid file by name from a grid list */
-/************************************************************************/
-
-static PJ_GRIDINFO* gridinfo_parent( PJ_GRIDINFO *gilist,
- const char *name, int length )
-{
- while( gilist )
- {
- if( strncmp(gilist->ct->id,name,length) == 0 ) return gilist;
- if( gilist->child )
- {
- PJ_GRIDINFO *parent=gridinfo_parent( gilist->child, name, length );
- if( parent ) return parent;
- }
- gilist=gilist->next;
- }
- return gilist;
-}
-
-/************************************************************************/
-/* pj_gridinfo_init_ntv2() */
-/* */
-/* Load a ntv2 (.gsb) file. */
-/************************************************************************/
-
-static int pj_gridinfo_init_ntv2( projCtx ctx, PAFile fid, PJ_GRIDINFO *gilist )
-{
- unsigned char header[11*16];
- int num_subfiles, subfile;
- int must_swap;
-
- /* cppcheck-suppress sizeofCalculation */
- STATIC_ASSERT( sizeof(pj_int32) == 4 );
- /* cppcheck-suppress sizeofCalculation */
- STATIC_ASSERT( sizeof(double) == 8 );
-
-/* -------------------------------------------------------------------- */
-/* Read the overview header. */
-/* -------------------------------------------------------------------- */
- if( pj_ctx_fread( ctx, header, sizeof(header), 1, fid ) != 1 )
- {
- pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- return 0;
- }
-
- if( header[8] == 11 )
- must_swap = !IS_LSB;
- else
- must_swap = IS_LSB;
-
-/* -------------------------------------------------------------------- */
-/* Byte swap interesting fields if needed. */
-/* -------------------------------------------------------------------- */
- if( must_swap )
- {
- swap_words( header+8, 4, 1 );
- swap_words( header+8+16, 4, 1 );
- swap_words( header+8+32, 4, 1 );
- swap_words( header+8+7*16, 8, 1 );
- swap_words( header+8+8*16, 8, 1 );
- swap_words( header+8+9*16, 8, 1 );
- swap_words( header+8+10*16, 8, 1 );
- }
-
-/* -------------------------------------------------------------------- */
-/* Get the subfile count out ... all we really use for now. */
-/* -------------------------------------------------------------------- */
- memcpy( &num_subfiles, header+8+32, 4 );
-
-/* ==================================================================== */
-/* Step through the subfiles, creating a PJ_GRIDINFO for each. */
-/* ==================================================================== */
- for( subfile = 0; subfile < num_subfiles; subfile++ )
- {
- struct CTABLE *ct;
- PJ_LP ur;
- int gs_count;
- PJ_GRIDINFO *gi;
-
-/* -------------------------------------------------------------------- */
-/* Read header. */
-/* -------------------------------------------------------------------- */
- if( pj_ctx_fread( ctx, header, sizeof(header), 1, fid ) != 1 )
- {
- pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- return 0;
- }
-
- if( strncmp((const char *) header,"SUB_NAME",8) != 0 )
- {
- pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- return 0;
- }
-
-/* -------------------------------------------------------------------- */
-/* Byte swap interesting fields if needed. */
-/* -------------------------------------------------------------------- */
- if( must_swap )
- {
- swap_words( header+8+16*4, 8, 1 );
- swap_words( header+8+16*5, 8, 1 );
- swap_words( header+8+16*6, 8, 1 );
- swap_words( header+8+16*7, 8, 1 );
- swap_words( header+8+16*8, 8, 1 );
- swap_words( header+8+16*9, 8, 1 );
- swap_words( header+8+16*10, 4, 1 );
- }
-
-/* -------------------------------------------------------------------- */
-/* Initialize a corresponding "ct" structure. */
-/* -------------------------------------------------------------------- */
- ct = (struct CTABLE *) pj_malloc(sizeof(struct CTABLE));
- if (!ct) {
- pj_ctx_set_errno(ctx, ENOMEM);
- return 0;
- }
- strncpy( ct->id, (const char *) header + 8, 8 );
- ct->id[8] = '\0';
-
- ct->ll.lam = - to_double(header+7*16+8); /* W_LONG */
- ct->ll.phi = to_double(header+4*16+8); /* S_LAT */
-
- ur.lam = - to_double(header+6*16+8); /* E_LONG */
- ur.phi = to_double(header+5*16+8); /* N_LAT */
-
- ct->del.lam = to_double(header+9*16+8);
- ct->del.phi = to_double(header+8*16+8);
-
- ct->lim.lam = (pj_int32) (fabs(ur.lam-ct->ll.lam)/ct->del.lam + 0.5) + 1;
- ct->lim.phi = (pj_int32) (fabs(ur.phi-ct->ll.phi)/ct->del.phi + 0.5) + 1;
-
- pj_log( ctx, PJ_LOG_DEBUG_MINOR,
- "NTv2 %s %dx%d: LL=(%.9g,%.9g) UR=(%.9g,%.9g)",
- ct->id,
- ct->lim.lam, ct->lim.phi,
- ct->ll.lam/3600.0, ct->ll.phi/3600.0,
- ur.lam/3600.0, ur.phi/3600.0 );
-
- ct->ll.lam *= DEG_TO_RAD/3600.0;
- ct->ll.phi *= DEG_TO_RAD/3600.0;
- ct->del.lam *= DEG_TO_RAD/3600.0;
- ct->del.phi *= DEG_TO_RAD/3600.0;
-
- memcpy( &gs_count, header + 8 + 16*10, 4 );
- if( gs_count != ct->lim.lam * ct->lim.phi )
- {
- pj_log( ctx, PJ_LOG_ERROR,
- "GS_COUNT(%d) does not match expected cells (%dx%d=%d)",
- gs_count, ct->lim.lam, ct->lim.phi,
- ct->lim.lam * ct->lim.phi );
- pj_dalloc(ct);
- pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- return 0;
- }
-
- ct->cvs = nullptr;
-
-/* -------------------------------------------------------------------- */
-/* Create a new gridinfo for this if we aren't processing the */
-/* 1st subfile, and initialize our grid info. */
-/* -------------------------------------------------------------------- */
- if( subfile == 0 )
- gi = gilist;
- else
- {
- gi = (PJ_GRIDINFO *) pj_calloc(1, sizeof(PJ_GRIDINFO));
- if (!gi) {
- pj_dalloc(ct);
- pj_gridinfo_free(ctx, gilist);
- pj_ctx_set_errno(ctx, ENOMEM);
- return 0;
- }
-
- gi->gridname = pj_strdup( gilist->gridname );
- gi->filename = pj_strdup( gilist->filename );
- if (!gi->gridname || !gi->filename) {
- pj_gridinfo_free(ctx, gi);
- pj_dalloc(ct);
- pj_gridinfo_free(ctx, gilist);
- pj_ctx_set_errno(ctx, ENOMEM);
- return 0;
- }
- gi->next = nullptr;
- }
-
- gi->must_swap = must_swap;
- gi->ct = ct;
- gi->format = "ntv2";
- gi->grid_offset = pj_ctx_ftell( ctx, fid );
-
-/* -------------------------------------------------------------------- */
-/* Attach to the correct list or sublist. */
-/* -------------------------------------------------------------------- */
- if( strncmp((const char *)header+24,"NONE",4) == 0 )
- {
- if( gi != gilist )
- {
- PJ_GRIDINFO *lnk;
-
- for( lnk = gilist; lnk->next != nullptr; lnk = lnk->next ) {}
- lnk->next = gi;
- }
- }
-
- else
- {
- PJ_GRIDINFO *lnk;
- PJ_GRIDINFO *gp = gridinfo_parent(gilist,
- (const char*)header+24,8);
-
- if( gp == nullptr )
- {
- pj_log( ctx, PJ_LOG_ERROR,
- "pj_gridinfo_init_ntv2(): "
- "failed to find parent %8.8s for %s.",
- (const char *) header+24, gi->ct->id );
-
- for( lnk = gilist; lnk->next != nullptr; lnk = lnk->next ) {}
- lnk->next = gi;
- }
- else
- {
- if( gp->child == nullptr )
- {
- gp->child = gi;
- }
- else
- {
- for( lnk = gp->child; lnk->next != nullptr; lnk = lnk->next ) {}
- lnk->next = gi;
- }
- }
- }
-
-/* -------------------------------------------------------------------- */
-/* Seek past the data. */
-/* -------------------------------------------------------------------- */
- pj_ctx_fseek( ctx, fid, gs_count * 16, SEEK_CUR );
- }
-
- return 1;
-}
-
-/************************************************************************/
-/* pj_gridinfo_init_ntv1() */
-/* */
-/* Load an NTv1 style Canadian grid shift file. */
-/************************************************************************/
-
-static int pj_gridinfo_init_ntv1( projCtx ctx, PAFile fid, PJ_GRIDINFO *gi )
-
-{
- unsigned char header[192]; /* 12 records of 16 bytes */
- struct CTABLE *ct;
- PJ_LP ur;
-
- /* cppcheck-suppress sizeofCalculation */
- STATIC_ASSERT( sizeof(pj_int32) == 4 );
- /* cppcheck-suppress sizeofCalculation */
- STATIC_ASSERT( sizeof(double) == 8 );
-
-/* -------------------------------------------------------------------- */
-/* Read the header. */
-/* -------------------------------------------------------------------- */
- if( pj_ctx_fread( ctx, header, sizeof(header), 1, fid ) != 1 )
- {
- pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- return 0;
- }
-
-/* -------------------------------------------------------------------- */
-/* Regularize fields of interest. */
-/* -------------------------------------------------------------------- */
- if( IS_LSB )
- {
- swap_words( header+8, 4, 1 );
- swap_words( header+24, 8, 1 );
- swap_words( header+40, 8, 1 );
- swap_words( header+56, 8, 1 );
- swap_words( header+72, 8, 1 );
- swap_words( header+88, 8, 1 );
- swap_words( header+104, 8, 1 );
- }
-
- if( *((int *) (header+8)) != 12 )
- {
- pj_log( ctx, PJ_LOG_ERROR,
- "NTv1 grid shift file has wrong record count, corrupt?" );
- pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- return 0;
- }
-
-/* -------------------------------------------------------------------- */
-/* Fill in CTABLE structure. */
-/* -------------------------------------------------------------------- */
- ct = (struct CTABLE *) pj_malloc(sizeof(struct CTABLE));
- if (!ct) {
- pj_ctx_set_errno(ctx, ENOMEM);
- return 0;
- }
- strcpy( ct->id, "NTv1 Grid Shift File" );
-
- ct->ll.lam = - to_double(header+72);
- ct->ll.phi = to_double(header+24);
- ur.lam = - to_double(header+56);
- ur.phi = to_double(header+40);
- ct->del.lam = to_double(header+104);
- ct->del.phi = to_double(header+88);
- ct->lim.lam = (pj_int32) (fabs(ur.lam-ct->ll.lam)/ct->del.lam + 0.5) + 1;
- ct->lim.phi = (pj_int32) (fabs(ur.phi-ct->ll.phi)/ct->del.phi + 0.5) + 1;
-
- pj_log( ctx, PJ_LOG_DEBUG_MINOR,
- "NTv1 %dx%d: LL=(%.9g,%.9g) UR=(%.9g,%.9g)",
- ct->lim.lam, ct->lim.phi,
- ct->ll.lam, ct->ll.phi, ur.lam, ur.phi );
-
- ct->ll.lam *= DEG_TO_RAD;
- ct->ll.phi *= DEG_TO_RAD;
- ct->del.lam *= DEG_TO_RAD;
- ct->del.phi *= DEG_TO_RAD;
- ct->cvs = nullptr;
-
- gi->ct = ct;
- gi->grid_offset = (long) sizeof(header);
- gi->format = "ntv1";
-
- return 1;
-}
-
-/************************************************************************/
-/* pj_gridinfo_init_gtx() */
-/* */
-/* Load a NOAA .gtx vertical datum shift file. */
-/************************************************************************/
-
-static int pj_gridinfo_init_gtx( projCtx ctx, PAFile fid, PJ_GRIDINFO *gi )
-
-{
- unsigned char header[40];
- struct CTABLE *ct;
- double xorigin,yorigin,xstep,ystep;
- int rows, columns;
-
- /* cppcheck-suppress sizeofCalculation */
- STATIC_ASSERT( sizeof(pj_int32) == 4 );
- /* cppcheck-suppress sizeofCalculation */
- STATIC_ASSERT( sizeof(double) == 8 );
-
-/* -------------------------------------------------------------------- */
-/* Read the header. */
-/* -------------------------------------------------------------------- */
- if( pj_ctx_fread( ctx, header, sizeof(header), 1, fid ) != 1 )
- {
- pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- return 0;
- }
-
-/* -------------------------------------------------------------------- */
-/* Regularize fields of interest and extract. */
-/* -------------------------------------------------------------------- */
- if( IS_LSB )
- {
- swap_words( header+0, 8, 4 );
- swap_words( header+32, 4, 2 );
- }
-
- memcpy( &yorigin, header+0, 8 );
- memcpy( &xorigin, header+8, 8 );
- memcpy( &ystep, header+16, 8 );
- memcpy( &xstep, header+24, 8 );
-
- memcpy( &rows, header+32, 4 );
- memcpy( &columns, header+36, 4 );
-
- if( xorigin < -360 || xorigin > 360
- || yorigin < -90 || yorigin > 90 )
- {
- pj_log( ctx, PJ_LOG_ERROR,
- "gtx file header has invalid extents, corrupt?");
- pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- return 0;
- }
-
-/* -------------------------------------------------------------------- */
-/* Fill in CTABLE structure. */
-/* -------------------------------------------------------------------- */
- ct = (struct CTABLE *) pj_malloc(sizeof(struct CTABLE));
- if (!ct) {
- pj_ctx_set_errno(ctx, ENOMEM);
- return 0;
- }
- strcpy( ct->id, "GTX Vertical Grid Shift File" );
-
- ct->ll.lam = xorigin;
- ct->ll.phi = yorigin;
- ct->del.lam = xstep;
- ct->del.phi = ystep;
- ct->lim.lam = columns;
- ct->lim.phi = rows;
-
- /* some GTX files come in 0-360 and we shift them back into the
- expected -180 to 180 range if possible. This does not solve
- problems with grids spanning the dateline. */
- if( ct->ll.lam >= 180.0 )
- ct->ll.lam -= 360.0;
-
- if( ct->ll.lam >= 0.0 && ct->ll.lam + ct->del.lam * ct->lim.lam > 180.0 )
- {
- pj_log( ctx, PJ_LOG_DEBUG_MAJOR,
- "This GTX spans the dateline! This will cause problems." );
- }
-
- pj_log( ctx, PJ_LOG_DEBUG_MINOR,
- "GTX %dx%d: LL=(%.9g,%.9g) UR=(%.9g,%.9g)",
- ct->lim.lam, ct->lim.phi,
- ct->ll.lam, ct->ll.phi,
- ct->ll.lam + (columns-1)*xstep, ct->ll.phi + (rows-1)*ystep);
-
- ct->ll.lam *= DEG_TO_RAD;
- ct->ll.phi *= DEG_TO_RAD;
- ct->del.lam *= DEG_TO_RAD;
- ct->del.phi *= DEG_TO_RAD;
- ct->cvs = nullptr;
-
- gi->ct = ct;
- gi->grid_offset = 40;
- gi->format = "gtx";
-
- return 1;
-}
-
-/************************************************************************/
-/* pj_gridinfo_init() */
-/* */
-/* Open and parse header details from a datum gridshift file */
-/* returning a list of PJ_GRIDINFOs for the grids in that */
-/* file. This superceeds use of nad_init() for modern */
-/* applications. */
-/************************************************************************/
-
-PJ_GRIDINFO *pj_gridinfo_init( projCtx ctx, const char *gridname )
-
-{
- PJ_GRIDINFO *gilist;
- PAFile fp;
- char header[160];
- size_t header_size = 0;
-
- errno = pj_errno = 0;
- ctx->last_errno = 0;
-
-/* -------------------------------------------------------------------- */
-/* Initialize a GRIDINFO with stub info we would use if it */
-/* cannot be loaded. */
-/* -------------------------------------------------------------------- */
- gilist = (PJ_GRIDINFO *) pj_calloc(1, sizeof(PJ_GRIDINFO));
- if (!gilist) {
- pj_ctx_set_errno(ctx, ENOMEM);
- return nullptr;
- }
-
- gilist->gridname = pj_strdup( gridname );
- if (!gilist->gridname) {
- pj_dalloc(gilist);
- pj_ctx_set_errno(ctx, ENOMEM);
- return nullptr;
- }
- gilist->filename = nullptr;
- gilist->format = "missing";
- gilist->grid_offset = 0;
- gilist->ct = nullptr;
- gilist->next = nullptr;
-
-/* -------------------------------------------------------------------- */
-/* Open the file using the usual search rules. */
-/* -------------------------------------------------------------------- */
- if (!(fp = pj_open_lib(ctx, gridname, "rb"))) {
- ctx->last_errno = 0; /* don't treat as a persistent error */
- return gilist;
- }
-
- gilist->filename = pj_strdup(gridname);
- if (!gilist->filename) {
- pj_dalloc(gilist->gridname);
- pj_dalloc(gilist);
- pj_ctx_set_errno(ctx, ENOMEM);
- return nullptr;
- }
-
-/* -------------------------------------------------------------------- */
-/* Load a header, to determine the file type. */
-/* -------------------------------------------------------------------- */
- if( (header_size = pj_ctx_fread( ctx, header, 1,
- sizeof(header), fp ) ) != sizeof(header) )
- {
- /* some files may be smaller that sizeof(header), eg 160, so */
- ctx->last_errno = 0; /* don't treat as a persistent error */
- pj_log( ctx, PJ_LOG_DEBUG_MAJOR,
- "pj_gridinfo_init: short header read of %d bytes",
- (int)header_size );
- }
-
- pj_ctx_fseek( ctx, fp, SEEK_SET, 0 );
-
-/* -------------------------------------------------------------------- */
-/* Determine file type. */
-/* -------------------------------------------------------------------- */
- if( header_size >= 144 + 16
- && strncmp(header + 0, "HEADER", 6) == 0
- && strncmp(header + 96, "W GRID", 6) == 0
- && strncmp(header + 144, "TO NAD83 ", 16) == 0 )
- {
- pj_gridinfo_init_ntv1( ctx, fp, gilist );
- }
-
- else if( header_size >= 48 + 7
- && strncmp(header + 0, "NUM_OREC", 8) == 0
- && strncmp(header + 48, "GS_TYPE", 7) == 0 )
- {
- pj_gridinfo_init_ntv2( ctx, fp, gilist );
- }
-
- else if( strlen(gridname) > 4
- && (strcmp(gridname+strlen(gridname)-3,"gtx") == 0
- || strcmp(gridname+strlen(gridname)-3,"GTX") == 0) )
- {
- pj_gridinfo_init_gtx( ctx, fp, gilist );
- }
-
- else if( header_size >= 9 && strncmp(header + 0,"CTABLE V2",9) == 0 )
- {
- struct CTABLE *ct = nad_ctable2_init( ctx, (struct projFileAPI_t*)fp );
-
- gilist->format = "ctable2";
- gilist->ct = ct;
-
- if (ct == nullptr)
- {
- pj_log( ctx, PJ_LOG_DEBUG_MAJOR,
- "CTABLE V2 ct is NULL.");
- }
- else
- {
- pj_log( ctx, PJ_LOG_DEBUG_MAJOR,
- "Ctable2 %s %dx%d: LL=(%.9g,%.9g) UR=(%.9g,%.9g)",
- ct->id,
- ct->lim.lam, ct->lim.phi,
- ct->ll.lam * RAD_TO_DEG, ct->ll.phi * RAD_TO_DEG,
- (ct->ll.lam + (ct->lim.lam-1)*ct->del.lam) * RAD_TO_DEG,
- (ct->ll.phi + (ct->lim.phi-1)*ct->del.phi) * RAD_TO_DEG );
- }
- }
-
- else
- {
- struct CTABLE *ct = nad_ctable_init( ctx, (struct projFileAPI_t*)fp );
- if (ct == nullptr)
- {
- pj_log( ctx, PJ_LOG_DEBUG_MAJOR,
- "CTABLE ct is NULL.");
- } else
- {
- gilist->format = "ctable";
- gilist->ct = ct;
-
- pj_log( ctx, PJ_LOG_DEBUG_MAJOR,
- "Ctable %s %dx%d: LL=(%.9g,%.9g) UR=(%.9g,%.9g)",
- ct->id,
- ct->lim.lam, ct->lim.phi,
- ct->ll.lam * RAD_TO_DEG, ct->ll.phi * RAD_TO_DEG,
- (ct->ll.lam + (ct->lim.lam-1)*ct->del.lam) * RAD_TO_DEG,
- (ct->ll.phi + (ct->lim.phi-1)*ct->del.phi) * RAD_TO_DEG );
- }
- }
-
- pj_ctx_fclose(ctx, fp);
-
- return gilist;
-}
diff --git a/src/gridlist.cpp b/src/gridlist.cpp
deleted file mode 100644
index c540b134..00000000
--- a/src/gridlist.cpp
+++ /dev/null
@@ -1,220 +0,0 @@
-/******************************************************************************
- * Project: PROJ.4
- * Purpose: Code to manage the list of currently loaded (cached) PJ_GRIDINFOs
- * See pj_gridinfo.c for details of loading individual grids.
- * Author: Frank Warmerdam, warmerdam@pobox.com
- *
- ******************************************************************************
- * Copyright (c) 2000, Frank Warmerdam <warmerdam@pobox.com>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included
- * in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- *****************************************************************************/
-
-#define PJ_LIB__
-
-#include <errno.h>
-#include <stddef.h>
-#include <string.h>
-
-#include "proj.h"
-#include "proj_internal.h"
-
-static PJ_GRIDINFO *grid_list = nullptr;
-#define PJ_MAX_PATH_LENGTH 1024
-
-/************************************************************************/
-/* pj_deallocate_grids() */
-/* */
-/* Deallocate all loaded grids. */
-/************************************************************************/
-
-void pj_deallocate_grids()
-
-{
- while( grid_list != nullptr )
- {
- PJ_GRIDINFO *item = grid_list;
- grid_list = grid_list->next;
- item->next = nullptr;
-
- pj_gridinfo_free( pj_get_default_ctx(), item );
- }
-}
-
-/************************************************************************/
-/* pj_gridlist_merge_grid() */
-/* */
-/* Find/load the named gridfile and merge it into the */
-/* last_nadgrids_list. */
-/************************************************************************/
-
-static int pj_gridlist_merge_gridfile( projCtx ctx,
- const char *gridname,
- PJ_GRIDINFO ***p_gridlist,
- int *p_gridcount,
- int *p_gridmax )
-
-{
- int got_match=0;
- PJ_GRIDINFO *this_grid, *tail = nullptr;
-
-/* -------------------------------------------------------------------- */
-/* Try to find in the existing list of loaded grids. Add all */
-/* matching grids as with NTv2 we can get many grids from one */
-/* file (one shared gridname). */
-/* -------------------------------------------------------------------- */
- for( this_grid = grid_list; this_grid != nullptr; this_grid = this_grid->next)
- {
- if( strcmp(this_grid->gridname,gridname) == 0 )
- {
- got_match = 1;
-
- /* don't add to the list if it is invalid. */
- if( this_grid->ct == nullptr )
- return 0;
-
- /* do we need to grow the list? */
- if( *p_gridcount >= *p_gridmax - 2 )
- {
- PJ_GRIDINFO **new_list;
- int new_max = *p_gridmax + 20;
-
- new_list = (PJ_GRIDINFO **) pj_calloc(new_max, sizeof(void *));
- if (!new_list) {
- pj_ctx_set_errno( ctx, ENOMEM );
- return 0;
- }
- if( *p_gridlist != nullptr )
- {
- memcpy( new_list, *p_gridlist,
- sizeof(void *) * (*p_gridmax) );
- pj_dalloc( *p_gridlist );
- }
-
- *p_gridlist = new_list;
- *p_gridmax = new_max;
- }
-
- /* add to the list */
- (*p_gridlist)[(*p_gridcount)++] = this_grid;
- (*p_gridlist)[*p_gridcount] = nullptr;
- }
-
- tail = this_grid;
- }
-
- if( got_match )
- return 1;
-
-/* -------------------------------------------------------------------- */
-/* Try to load the named grid. */
-/* -------------------------------------------------------------------- */
- this_grid = pj_gridinfo_init( ctx, gridname );
-
- if( this_grid == nullptr )
- {
- return 0;
- }
-
- if( tail != nullptr )
- tail->next = this_grid;
- else
- grid_list = this_grid;
-
-/* -------------------------------------------------------------------- */
-/* Recurse to add the grid now that it is loaded. */
-/* -------------------------------------------------------------------- */
- return pj_gridlist_merge_gridfile( ctx, gridname, p_gridlist,
- p_gridcount, p_gridmax );
-}
-
-/************************************************************************/
-/* pj_gridlist_from_nadgrids() */
-/* */
-/* This functions loads the list of grids corresponding to a */
-/* particular nadgrids string into a list, and returns it. The */
-/* list is kept around till a request is made with a different */
-/* string in order to cut down on the string parsing cost, and */
-/* the cost of building the list of tables each time. */
-/************************************************************************/
-
-PJ_GRIDINFO **pj_gridlist_from_nadgrids( projCtx ctx, const char *nadgrids,
- int *grid_count)
-
-{
- const char *s;
- PJ_GRIDINFO **gridlist = nullptr;
- int grid_max = 0;
-
- pj_errno = 0;
- *grid_count = 0;
-
- pj_acquire_lock();
-
-/* -------------------------------------------------------------------- */
-/* Loop processing names out of nadgrids one at a time. */
-/* -------------------------------------------------------------------- */
- for( s = nadgrids; *s != '\0'; )
- {
- size_t end_char;
- int required = 1;
- char name[PJ_MAX_PATH_LENGTH];
-
- if( *s == '@' )
- {
- required = 0;
- s++;
- }
-
- for( end_char = 0;
- s[end_char] != '\0' && s[end_char] != ',';
- end_char++ ) {}
-
- if( end_char >= sizeof(name) )
- {
- pj_dalloc( gridlist );
- pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- pj_release_lock();
- return nullptr;
- }
-
- strncpy( name, s, end_char );
- name[end_char] = '\0';
-
- s += end_char;
- if( *s == ',' )
- s++;
-
- if( !pj_gridlist_merge_gridfile( ctx, name, &gridlist, grid_count,
- &grid_max)
- && required )
- {
- pj_dalloc( gridlist );
- pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- pj_release_lock();
- return nullptr;
- }
- else
- pj_errno = 0;
- }
-
- pj_release_lock();
-
- return gridlist;
-}
diff --git a/src/grids.cpp b/src/grids.cpp
new file mode 100644
index 00000000..68bf8600
--- /dev/null
+++ b/src/grids.cpp
@@ -0,0 +1,3402 @@
+/******************************************************************************
+ * Project: PROJ
+ * Purpose: Grid management
+ * Author: Even Rouault, <even.rouault at spatialys.com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2000, Frank Warmerdam <warmerdam@pobox.com>
+ * Copyright (c) 2019, Even Rouault, <even.rouault at spatialys.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *****************************************************************************/
+
+#ifndef FROM_PROJ_CPP
+#define FROM_PROJ_CPP
+#endif
+#define LRU11_DO_NOT_DEFINE_OUT_OF_CLASS_METHODS
+
+#include "grids.hpp"
+#include "filemanager.hpp"
+#include "proj/internal/internal.hpp"
+#include "proj/internal/lru_cache.hpp"
+#include "proj_internal.h"
+
+#ifdef TIFF_ENABLED
+#include "tiffio.h"
+#endif
+
+#include <algorithm>
+#include <cmath>
+
+NS_PROJ_START
+
+using namespace internal;
+
+/************************************************************************/
+/* swap_words() */
+/* */
+/* Convert the byte order of the given word(s) in place. */
+/************************************************************************/
+
+static const int byte_order_test = 1;
+#define IS_LSB (1 == ((const unsigned char *)(&byte_order_test))[0])
+
+static void swap_words(void *dataIn, size_t word_size, size_t word_count)
+
+{
+ unsigned char *data = static_cast<unsigned char *>(dataIn);
+ for (size_t word = 0; word < word_count; word++) {
+ for (size_t i = 0; i < word_size / 2; i++) {
+ unsigned char t;
+
+ t = data[i];
+ data[i] = data[word_size - i - 1];
+ data[word_size - i - 1] = t;
+ }
+
+ data += word_size;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+bool ExtentAndRes::fullWorldLongitude() const {
+ return eastLon - westLon + resLon >= 2 * M_PI - 1e-10;
+}
+
+// ---------------------------------------------------------------------------
+
+bool ExtentAndRes::contains(const ExtentAndRes &other) const {
+ return other.westLon >= westLon && other.eastLon <= eastLon &&
+ other.southLat >= southLat && other.northLat <= northLat;
+}
+
+// ---------------------------------------------------------------------------
+
+bool ExtentAndRes::intersects(const ExtentAndRes &other) const {
+ return other.westLon < eastLon && westLon <= other.westLon &&
+ other.southLat < northLat && southLat <= other.northLat;
+}
+
+// ---------------------------------------------------------------------------
+
+Grid::Grid(const std::string &name, int widthIn, int heightIn,
+ const ExtentAndRes &extentIn)
+ : m_name(name), m_width(widthIn), m_height(heightIn), m_extent(extentIn) {}
+
+// ---------------------------------------------------------------------------
+
+Grid::~Grid() = default;
+
+// ---------------------------------------------------------------------------
+
+VerticalShiftGrid::VerticalShiftGrid(const std::string &nameIn, int widthIn,
+ int heightIn, const ExtentAndRes &extentIn)
+ : Grid(nameIn, widthIn, heightIn, extentIn) {}
+
+// ---------------------------------------------------------------------------
+
+VerticalShiftGrid::~VerticalShiftGrid() = default;
+
+// ---------------------------------------------------------------------------
+
+static ExtentAndRes globalExtent() {
+ ExtentAndRes extent;
+ extent.westLon = -M_PI;
+ extent.southLat = -M_PI / 2;
+ extent.eastLon = M_PI;
+ extent.northLat = M_PI / 2;
+ extent.resLon = M_PI;
+ extent.resLat = M_PI / 2;
+ return extent;
+}
+
+// ---------------------------------------------------------------------------
+
+class NullVerticalShiftGrid : public VerticalShiftGrid {
+
+ public:
+ NullVerticalShiftGrid() : VerticalShiftGrid("null", 3, 3, globalExtent()) {}
+
+ bool isNullGrid() const override { return true; }
+ bool valueAt(int, int, float &out) const override;
+ bool isNodata(float, double) const override { return false; }
+ void reassign_context(PJ_CONTEXT *) override {}
+ bool hasChanged() const override { return false; }
+};
+
+// ---------------------------------------------------------------------------
+
+bool NullVerticalShiftGrid::valueAt(int, int, float &out) const {
+ out = 0.0f;
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+class GTXVerticalShiftGrid : public VerticalShiftGrid {
+ PJ_CONTEXT *m_ctx;
+ std::unique_ptr<File> m_fp;
+
+ GTXVerticalShiftGrid(const GTXVerticalShiftGrid &) = delete;
+ GTXVerticalShiftGrid &operator=(const GTXVerticalShiftGrid &) = delete;
+
+ public:
+ explicit GTXVerticalShiftGrid(PJ_CONTEXT *ctx, std::unique_ptr<File> &&fp,
+ const std::string &nameIn, int widthIn,
+ int heightIn, const ExtentAndRes &extentIn)
+ : VerticalShiftGrid(nameIn, widthIn, heightIn, extentIn), m_ctx(ctx),
+ m_fp(std::move(fp)) {}
+
+ ~GTXVerticalShiftGrid() override;
+
+ bool valueAt(int x, int y, float &out) const override;
+ bool isNodata(float val, double multiplier) const override;
+
+ static GTXVerticalShiftGrid *open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
+ const std::string &name);
+
+ void reassign_context(PJ_CONTEXT *ctx) override {
+ m_ctx = ctx;
+ m_fp->reassign_context(ctx);
+ }
+
+ bool hasChanged() const override { return m_fp->hasChanged(); }
+};
+
+// ---------------------------------------------------------------------------
+
+GTXVerticalShiftGrid::~GTXVerticalShiftGrid() = default;
+
+// ---------------------------------------------------------------------------
+
+GTXVerticalShiftGrid *GTXVerticalShiftGrid::open(PJ_CONTEXT *ctx,
+ std::unique_ptr<File> fp,
+ const std::string &name) {
+ unsigned char header[40];
+
+ /* -------------------------------------------------------------------- */
+ /* Read the header. */
+ /* -------------------------------------------------------------------- */
+ if (fp->read(header, sizeof(header)) != sizeof(header)) {
+ pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ return nullptr;
+ }
+
+ /* -------------------------------------------------------------------- */
+ /* Regularize fields of interest and extract. */
+ /* -------------------------------------------------------------------- */
+ if (IS_LSB) {
+ swap_words(header + 0, 8, 4);
+ swap_words(header + 32, 4, 2);
+ }
+
+ double xorigin, yorigin, xstep, ystep;
+ int rows, columns;
+
+ memcpy(&yorigin, header + 0, 8);
+ memcpy(&xorigin, header + 8, 8);
+ memcpy(&ystep, header + 16, 8);
+ memcpy(&xstep, header + 24, 8);
+
+ memcpy(&rows, header + 32, 4);
+ memcpy(&columns, header + 36, 4);
+
+ if (xorigin < -360 || xorigin > 360 || yorigin < -90 || yorigin > 90) {
+ pj_log(ctx, PJ_LOG_ERROR,
+ "gtx file header has invalid extents, corrupt?");
+ pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ return nullptr;
+ }
+
+ /* some GTX files come in 0-360 and we shift them back into the
+ expected -180 to 180 range if possible. This does not solve
+ problems with grids spanning the dateline. */
+ if (xorigin >= 180.0)
+ xorigin -= 360.0;
+
+ if (xorigin >= 0.0 && xorigin + xstep * columns > 180.0) {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "This GTX spans the dateline! This will cause problems.");
+ }
+
+ ExtentAndRes extent;
+ extent.westLon = xorigin * DEG_TO_RAD;
+ extent.southLat = yorigin * DEG_TO_RAD;
+ extent.resLon = xstep * DEG_TO_RAD;
+ extent.resLat = ystep * DEG_TO_RAD;
+ extent.eastLon = (xorigin + xstep * (columns - 1)) * DEG_TO_RAD;
+ extent.northLat = (yorigin + ystep * (rows - 1)) * DEG_TO_RAD;
+
+ return new GTXVerticalShiftGrid(ctx, std::move(fp), name, columns, rows,
+ extent);
+}
+
+// ---------------------------------------------------------------------------
+
+bool GTXVerticalShiftGrid::valueAt(int x, int y, float &out) const {
+ assert(x >= 0 && y >= 0 && x < m_width && y < m_height);
+
+ m_fp->seek(40 + sizeof(float) * (y * m_width + x));
+ if (m_fp->read(&out, sizeof(out)) != sizeof(out)) {
+ pj_ctx_set_errno(m_ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ return false;
+ }
+ if (IS_LSB) {
+ swap_words(&out, sizeof(float), 1);
+ }
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+bool GTXVerticalShiftGrid::isNodata(float val, double multiplier) const {
+ /* nodata? */
+ /* GTX official nodata value if -88.88880f, but some grids also */
+ /* use other big values for nodata (e.g naptrans2008.gtx has */
+ /* nodata values like -2147479936), so test them too */
+ return val * multiplier > 1000 || val * multiplier < -1000 ||
+ val == -88.88880f;
+}
+
+// ---------------------------------------------------------------------------
+
+VerticalShiftGridSet::VerticalShiftGridSet() = default;
+
+// ---------------------------------------------------------------------------
+
+VerticalShiftGridSet::~VerticalShiftGridSet() = default;
+
+// ---------------------------------------------------------------------------
+
+static bool IsTIFF(size_t header_size, const unsigned char *header) {
+ // Test combinations of signature for ClassicTIFF/BigTIFF little/big endian
+ return header_size >= 4 && (((header[0] == 'I' && header[1] == 'I') ||
+ (header[0] == 'M' && header[1] == 'M')) &&
+ ((header[2] == 0x2A && header[3] == 0) ||
+ (header[3] == 0x2A && header[2] == 0) ||
+ (header[2] == 0x2B && header[3] == 0) ||
+ (header[3] == 0x2B && header[2] == 0)));
+}
+
+#ifdef TIFF_ENABLED
+
+// ---------------------------------------------------------------------------
+
+enum class TIFFDataType { Int16, UInt16, Int32, UInt32, Float32, Float64 };
+
+// ---------------------------------------------------------------------------
+
+constexpr uint16 TIFFTAG_GEOPIXELSCALE = 33550;
+constexpr uint16 TIFFTAG_GEOTIEPOINTS = 33922;
+constexpr uint16 TIFFTAG_GEOTRANSMATRIX = 34264;
+constexpr uint16 TIFFTAG_GEOKEYDIRECTORY = 34735;
+constexpr uint16 TIFFTAG_GEODOUBLEPARAMS = 34736;
+constexpr uint16 TIFFTAG_GEOASCIIPARAMS = 34737;
+constexpr uint16 TIFFTAG_GDAL_METADATA = 42112;
+constexpr uint16 TIFFTAG_GDAL_NODATA = 42113;
+
+// ---------------------------------------------------------------------------
+
+class BlockCache {
+ public:
+ void insert(uint32 ifdIdx, uint32 blockNumber,
+ const std::vector<unsigned char> &data);
+ std::shared_ptr<std::vector<unsigned char>> get(uint32 ifdIdx,
+ uint32 blockNumber);
+
+ private:
+ struct Key {
+ uint32 ifdIdx;
+ uint32 blockNumber;
+
+ Key(uint32 ifdIdxIn, uint32 blockNumberIn)
+ : ifdIdx(ifdIdxIn), blockNumber(blockNumberIn) {}
+ bool operator==(const Key &other) const {
+ return ifdIdx == other.ifdIdx && blockNumber == other.blockNumber;
+ }
+ };
+
+ struct KeyHasher {
+ std::size_t operator()(const Key &k) const {
+ return k.ifdIdx ^ (k.blockNumber << 16) ^ (k.blockNumber >> 16);
+ }
+ };
+
+ static constexpr int NUM_BLOCKS_AT_CROSSING_TILES = 4;
+ static constexpr int MAX_SAMPLE_COUNT = 3;
+ lru11::Cache<
+ Key, std::shared_ptr<std::vector<unsigned char>>, lru11::NullLock,
+ std::unordered_map<
+ Key,
+ typename std::list<lru11::KeyValuePair<
+ Key, std::shared_ptr<std::vector<unsigned char>>>>::iterator,
+ KeyHasher>>
+ cache_{NUM_BLOCKS_AT_CROSSING_TILES * MAX_SAMPLE_COUNT};
+};
+
+// ---------------------------------------------------------------------------
+
+void BlockCache::insert(uint32 ifdIdx, uint32 blockNumber,
+ const std::vector<unsigned char> &data) {
+ cache_.insert(Key(ifdIdx, blockNumber),
+ std::make_shared<std::vector<unsigned char>>(data));
+}
+
+// ---------------------------------------------------------------------------
+
+std::shared_ptr<std::vector<unsigned char>>
+BlockCache::get(uint32 ifdIdx, uint32 blockNumber) {
+ std::shared_ptr<std::vector<unsigned char>> ret;
+ cache_.tryGet(Key(ifdIdx, blockNumber), ret);
+ return ret;
+}
+
+// ---------------------------------------------------------------------------
+
+class GTiffGrid : public Grid {
+ PJ_CONTEXT *m_ctx; // owned by the belonging GTiffDataset
+ TIFF *m_hTIFF; // owned by the belonging GTiffDataset
+ BlockCache &m_cache; // owned by the belonging GTiffDataset
+ File *m_fp; // owned by the belonging GTiffDataset
+ uint32 m_ifdIdx;
+ TIFFDataType m_dt;
+ uint16 m_samplesPerPixel;
+ uint16 m_planarConfig;
+ bool m_bottomUp;
+ toff_t m_dirOffset;
+ bool m_tiled;
+ uint32 m_blockWidth = 0;
+ uint32 m_blockHeight = 0;
+ mutable std::vector<unsigned char> m_buffer{};
+ unsigned m_blocksPerRow = 0;
+ unsigned m_blocksPerCol = 0;
+ std::map<int, double> m_mapOffset{};
+ std::map<int, double> m_mapScale{};
+ std::map<std::pair<int, std::string>, std::string> m_metadata{};
+ bool m_hasNodata = false;
+ float m_noData = 0.0f;
+ uint32 m_subfileType = 0;
+
+ GTiffGrid(const GTiffGrid &) = delete;
+ GTiffGrid &operator=(const GTiffGrid &) = delete;
+
+ void getScaleOffset(double &scale, double &offset, uint16 sample) const;
+
+ template <class T>
+ float readValue(const std::vector<unsigned char> &buffer,
+ uint32 offsetInBlock, uint16 sample) const;
+
+ public:
+ GTiffGrid(PJ_CONTEXT *ctx, TIFF *hTIFF, BlockCache &cache, File *fp,
+ uint32 ifdIdx, const std::string &nameIn, int widthIn,
+ int heightIn, const ExtentAndRes &extentIn, TIFFDataType dtIn,
+ uint16 samplesPerPixelIn, uint16 planarConfig, bool bottomUpIn);
+
+ ~GTiffGrid() override;
+
+ uint16 samplesPerPixel() const { return m_samplesPerPixel; }
+
+ bool valueAt(uint16 sample, int x, int y, float &out) const;
+
+ bool isNodata(float val) const;
+
+ std::string metadataItem(const std::string &key, int sample = -1) const;
+
+ uint32 subfileType() const { return m_subfileType; }
+
+ void reassign_context(PJ_CONTEXT *ctx) { m_ctx = ctx; }
+
+ bool hasChanged() const override { return m_fp->hasChanged(); }
+};
+
+// ---------------------------------------------------------------------------
+
+GTiffGrid::GTiffGrid(PJ_CONTEXT *ctx, TIFF *hTIFF, BlockCache &cache, File *fp,
+ uint32 ifdIdx, const std::string &nameIn, int widthIn,
+ int heightIn, const ExtentAndRes &extentIn,
+ TIFFDataType dtIn, uint16 samplesPerPixelIn,
+ uint16 planarConfig, bool bottomUpIn)
+ : Grid(nameIn, widthIn, heightIn, extentIn), m_ctx(ctx), m_hTIFF(hTIFF),
+ m_cache(cache), m_fp(fp), m_ifdIdx(ifdIdx), m_dt(dtIn),
+ m_samplesPerPixel(samplesPerPixelIn), m_planarConfig(planarConfig),
+ m_bottomUp(bottomUpIn), m_dirOffset(TIFFCurrentDirOffset(hTIFF)),
+ m_tiled(TIFFIsTiled(hTIFF) != 0) {
+
+ if (m_tiled) {
+ TIFFGetField(m_hTIFF, TIFFTAG_TILEWIDTH, &m_blockWidth);
+ TIFFGetField(m_hTIFF, TIFFTAG_TILELENGTH, &m_blockHeight);
+ } else {
+ m_blockWidth = widthIn;
+ TIFFGetField(m_hTIFF, TIFFTAG_ROWSPERSTRIP, &m_blockHeight);
+ if (m_blockHeight > static_cast<unsigned>(m_height))
+ m_blockHeight = m_height;
+ }
+
+ TIFFGetField(m_hTIFF, TIFFTAG_SUBFILETYPE, &m_subfileType);
+
+ m_blocksPerRow = (m_width + m_blockWidth - 1) / m_blockWidth;
+ m_blocksPerCol = (m_height + m_blockHeight - 1) / m_blockHeight;
+
+ const char *text = nullptr;
+ // Poor-man XML parsing of TIFFTAG_GDAL_METADATA tag. Hopefully good
+ // enough for our purposes.
+ if (TIFFGetField(m_hTIFF, TIFFTAG_GDAL_METADATA, &text)) {
+ const char *ptr = text;
+ while (true) {
+ ptr = strstr(ptr, "<Item ");
+ if (ptr == nullptr)
+ break;
+ const char *endTag = strchr(ptr, '>');
+ if (endTag == nullptr)
+ break;
+ const char *endValue = strchr(endTag, '<');
+ if (endValue == nullptr)
+ break;
+
+ std::string tag;
+ tag.append(ptr, endTag - ptr);
+
+ std::string value;
+ value.append(endTag + 1, endValue - (endTag + 1));
+
+ std::string name;
+ auto namePos = tag.find("name=\"");
+ if (namePos == std::string::npos)
+ break;
+ {
+ namePos += strlen("name=\"");
+ const auto endQuote = tag.find('"', namePos);
+ if (endQuote == std::string::npos)
+ break;
+ name = tag.substr(namePos, endQuote - namePos);
+ }
+
+ const auto samplePos = tag.find("sample=\"");
+ int sample = -1;
+ if (samplePos != std::string::npos) {
+ sample = atoi(tag.c_str() + samplePos + strlen("sample=\""));
+ }
+
+ m_metadata[std::pair<int, std::string>(sample, name)] = value;
+
+ auto rolePos = tag.find("role=\"");
+ if (rolePos != std::string::npos) {
+ rolePos += strlen("role=\"");
+ const auto endQuote = tag.find('"', rolePos);
+ if (endQuote == std::string::npos)
+ break;
+ const auto role = tag.substr(rolePos, endQuote - rolePos);
+ if (role == "offset") {
+ if (sample >= 0) {
+ try {
+ m_mapOffset[sample] = c_locale_stod(value);
+ } catch (const std::exception &) {
+ }
+ }
+ } else if (role == "scale") {
+ if (sample >= 0) {
+ try {
+ m_mapScale[sample] = c_locale_stod(value);
+ } catch (const std::exception &) {
+ }
+ }
+ }
+ }
+
+ ptr = endValue + 1;
+ }
+ }
+
+ if (TIFFGetField(m_hTIFF, TIFFTAG_GDAL_NODATA, &text)) {
+ try {
+ m_noData = static_cast<float>(c_locale_stod(text));
+ m_hasNodata = true;
+ } catch (const std::exception &) {
+ }
+ }
+
+ auto oIter = m_metadata.find(std::pair<int, std::string>(-1, "grid_name"));
+ if (oIter != m_metadata.end()) {
+ m_name += ", " + oIter->second;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+GTiffGrid::~GTiffGrid() = default;
+
+// ---------------------------------------------------------------------------
+
+void GTiffGrid::getScaleOffset(double &scale, double &offset,
+ uint16 sample) const {
+ {
+ auto iter = m_mapScale.find(sample);
+ if (iter != m_mapScale.end())
+ scale = iter->second;
+ }
+
+ {
+ auto iter = m_mapOffset.find(sample);
+ if (iter != m_mapOffset.end())
+ offset = iter->second;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+template <class T>
+float GTiffGrid::readValue(const std::vector<unsigned char> &buffer,
+ uint32 offsetInBlock, uint16 sample) const {
+ const auto ptr = reinterpret_cast<const T *>(buffer.data());
+ assert(offsetInBlock < buffer.size() / sizeof(T));
+ const auto val = ptr[offsetInBlock];
+ if (!m_hasNodata || static_cast<float>(val) != m_noData) {
+ double scale = 1;
+ double offset = 0;
+ getScaleOffset(scale, offset, sample);
+ return static_cast<float>(val * scale + offset);
+ } else {
+ return static_cast<float>(val);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+bool GTiffGrid::valueAt(uint16 sample, int x, int yFromBottom,
+ float &out) const {
+ assert(x >= 0 && yFromBottom >= 0 && x < m_width && yFromBottom < m_height);
+ assert(sample < m_samplesPerPixel);
+
+ const int blockX = x / m_blockWidth;
+
+ // All non-TIFF grids have the first rows in the file being the one
+ // corresponding to the southern-most row. In GeoTIFF, the convention is
+ // *generally* different (when m_bottomUp == false), TIFF being an
+ // image-oriented image. If m_bottomUp == true, then we had GeoTIFF hints
+ // that the first row of the image is the southern-most.
+ const int yTIFF = m_bottomUp ? yFromBottom : m_height - 1 - yFromBottom;
+ const int blockY = yTIFF / m_blockHeight;
+
+ uint32 blockId = blockY * m_blocksPerRow + blockX;
+ if (m_planarConfig == PLANARCONFIG_SEPARATE) {
+ blockId += sample * m_blocksPerCol * m_blocksPerRow;
+ }
+
+ auto cachedBuffer = m_cache.get(m_ifdIdx, blockId);
+ std::vector<unsigned char> *pBuffer = &m_buffer;
+ if (cachedBuffer != nullptr) {
+ // Safe as we don't access the cache before pBuffer is used
+ pBuffer = cachedBuffer.get();
+ } else {
+ if (TIFFCurrentDirOffset(m_hTIFF) != m_dirOffset &&
+ !TIFFSetSubDirectory(m_hTIFF, m_dirOffset)) {
+ return false;
+ }
+ if (m_buffer.empty()) {
+ const auto blockSize = static_cast<size_t>(
+ m_tiled ? TIFFTileSize64(m_hTIFF) : TIFFStripSize64(m_hTIFF));
+ try {
+ m_buffer.resize(blockSize);
+ } catch (const std::exception &e) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Exception %s", e.what());
+ return false;
+ }
+ }
+
+ if (m_tiled) {
+ if (TIFFReadEncodedTile(m_hTIFF, blockId, m_buffer.data(),
+ m_buffer.size()) == -1) {
+ return false;
+ }
+ } else {
+ if (TIFFReadEncodedStrip(m_hTIFF, blockId, m_buffer.data(),
+ m_buffer.size()) == -1) {
+ return false;
+ }
+ }
+
+ try {
+ m_cache.insert(m_ifdIdx, blockId, m_buffer);
+ } catch (const std::exception &e) {
+ // Should normally not happen
+ pj_log(m_ctx, PJ_LOG_ERROR, "Exception %s", e.what());
+ }
+ }
+
+ uint32 offsetInBlock =
+ (x % m_blockWidth) + (yTIFF % m_blockHeight) * m_blockWidth;
+ if (m_planarConfig == PLANARCONFIG_CONTIG)
+ offsetInBlock = offsetInBlock * m_samplesPerPixel + sample;
+
+ switch (m_dt) {
+ case TIFFDataType::Int16:
+ out = readValue<short>(*pBuffer, offsetInBlock, sample);
+ break;
+
+ case TIFFDataType::UInt16:
+ out = readValue<unsigned short>(*pBuffer, offsetInBlock, sample);
+ break;
+
+ case TIFFDataType::Int32:
+ out = readValue<int>(*pBuffer, offsetInBlock, sample);
+ break;
+
+ case TIFFDataType::UInt32:
+ out = readValue<unsigned int>(*pBuffer, offsetInBlock, sample);
+ break;
+
+ case TIFFDataType::Float32:
+ out = readValue<float>(*pBuffer, offsetInBlock, sample);
+ break;
+
+ case TIFFDataType::Float64:
+ out = readValue<double>(*pBuffer, offsetInBlock, sample);
+ break;
+ }
+
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+bool GTiffGrid::isNodata(float val) const {
+ return (m_hasNodata && val == m_noData) || std::isnan(val);
+}
+
+// ---------------------------------------------------------------------------
+
+std::string GTiffGrid::metadataItem(const std::string &key, int sample) const {
+ auto iter = m_metadata.find(std::pair<int, std::string>(sample, key));
+ if (iter == m_metadata.end()) {
+ return std::string();
+ }
+ return iter->second;
+}
+
+// ---------------------------------------------------------------------------
+
+class GTiffDataset {
+ PJ_CONTEXT *m_ctx;
+ std::unique_ptr<File> m_fp;
+ TIFF *m_hTIFF = nullptr;
+ bool m_hasNextGrid = false;
+ uint32 m_ifdIdx = 0;
+ toff_t m_nextDirOffset = 0;
+ std::string m_filename{};
+ BlockCache m_cache{};
+
+ GTiffDataset(const GTiffDataset &) = delete;
+ GTiffDataset &operator=(const GTiffDataset &) = delete;
+
+ // libtiff I/O routines
+ static tsize_t tiffReadProc(thandle_t fd, tdata_t buf, tsize_t size) {
+ GTiffDataset *self = static_cast<GTiffDataset *>(fd);
+ return self->m_fp->read(buf, size);
+ }
+
+ static tsize_t tiffWriteProc(thandle_t, tdata_t, tsize_t) {
+ assert(false);
+ return 0;
+ }
+
+ static toff_t tiffSeekProc(thandle_t fd, toff_t off, int whence) {
+ GTiffDataset *self = static_cast<GTiffDataset *>(fd);
+ if (self->m_fp->seek(off, whence))
+ return static_cast<toff_t>(self->m_fp->tell());
+ else
+ return static_cast<toff_t>(-1);
+ }
+
+ static int tiffCloseProc(thandle_t) {
+ // done in destructor
+ return 0;
+ }
+
+ static toff_t tiffSizeProc(thandle_t fd) {
+ GTiffDataset *self = static_cast<GTiffDataset *>(fd);
+ const auto old_off = self->m_fp->tell();
+ self->m_fp->seek(0, SEEK_END);
+ const auto file_size = static_cast<toff_t>(self->m_fp->tell());
+ self->m_fp->seek(old_off);
+ return file_size;
+ }
+
+ static int tiffMapProc(thandle_t, tdata_t *, toff_t *) { return (0); }
+
+ static void tiffUnmapProc(thandle_t, tdata_t, toff_t) {}
+
+ public:
+ GTiffDataset(PJ_CONTEXT *ctx, std::unique_ptr<File> &&fp)
+ : m_ctx(ctx), m_fp(std::move(fp)) {}
+ virtual ~GTiffDataset();
+
+ bool openTIFF(const std::string &filename);
+
+ std::unique_ptr<GTiffGrid> nextGrid();
+
+ void reassign_context(PJ_CONTEXT *ctx) {
+ m_ctx = ctx;
+ m_fp->reassign_context(ctx);
+ }
+};
+
+// ---------------------------------------------------------------------------
+
+GTiffDataset::~GTiffDataset() {
+ if (m_hTIFF)
+ TIFFClose(m_hTIFF);
+}
+
+// ---------------------------------------------------------------------------
+class OneTimeTIFFTagInit {
+
+ static TIFFExtendProc ParentExtender;
+
+ // Function called by libtiff when initializing a TIFF directory
+ static void GTiffTagExtender(TIFF *tif) {
+ static const TIFFFieldInfo xtiffFieldInfo[] = {
+ // GeoTIFF tags
+ {TIFFTAG_GEOPIXELSCALE, -1, -1, TIFF_DOUBLE, FIELD_CUSTOM, TRUE,
+ TRUE, const_cast<char *>("GeoPixelScale")},
+ {TIFFTAG_GEOTIEPOINTS, -1, -1, TIFF_DOUBLE, FIELD_CUSTOM, TRUE,
+ TRUE, const_cast<char *>("GeoTiePoints")},
+ {TIFFTAG_GEOTRANSMATRIX, -1, -1, TIFF_DOUBLE, FIELD_CUSTOM, TRUE,
+ TRUE, const_cast<char *>("GeoTransformationMatrix")},
+
+ {TIFFTAG_GEOKEYDIRECTORY, -1, -1, TIFF_SHORT, FIELD_CUSTOM, TRUE,
+ TRUE, const_cast<char *>("GeoKeyDirectory")},
+ {TIFFTAG_GEODOUBLEPARAMS, -1, -1, TIFF_DOUBLE, FIELD_CUSTOM, TRUE,
+ TRUE, const_cast<char *>("GeoDoubleParams")},
+ {TIFFTAG_GEOASCIIPARAMS, -1, -1, TIFF_ASCII, FIELD_CUSTOM, TRUE,
+ FALSE, const_cast<char *>("GeoASCIIParams")},
+
+ // GDAL tags
+ {TIFFTAG_GDAL_METADATA, -1, -1, TIFF_ASCII, FIELD_CUSTOM, TRUE,
+ FALSE, const_cast<char *>("GDALMetadata")},
+ {TIFFTAG_GDAL_NODATA, -1, -1, TIFF_ASCII, FIELD_CUSTOM, TRUE, FALSE,
+ const_cast<char *>("GDALNoDataValue")},
+
+ };
+
+ if (ParentExtender)
+ (*ParentExtender)(tif);
+
+ TIFFMergeFieldInfo(tif, xtiffFieldInfo,
+ sizeof(xtiffFieldInfo) / sizeof(xtiffFieldInfo[0]));
+ }
+
+ public:
+ OneTimeTIFFTagInit() {
+ assert(ParentExtender == nullptr);
+ // Install our TIFF tag extender
+ ParentExtender = TIFFSetTagExtender(GTiffTagExtender);
+ }
+};
+
+TIFFExtendProc OneTimeTIFFTagInit::ParentExtender = nullptr;
+
+// ---------------------------------------------------------------------------
+
+bool GTiffDataset::openTIFF(const std::string &filename) {
+ static OneTimeTIFFTagInit oneTimeTIFFTagInit;
+ m_hTIFF =
+ TIFFClientOpen(filename.c_str(), "r", static_cast<thandle_t>(this),
+ GTiffDataset::tiffReadProc, GTiffDataset::tiffWriteProc,
+ GTiffDataset::tiffSeekProc, GTiffDataset::tiffCloseProc,
+ GTiffDataset::tiffSizeProc, GTiffDataset::tiffMapProc,
+ GTiffDataset::tiffUnmapProc);
+
+ m_filename = filename;
+ m_hasNextGrid = true;
+ return m_hTIFF != nullptr;
+}
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<GTiffGrid> GTiffDataset::nextGrid() {
+ if (!m_hasNextGrid)
+ return nullptr;
+ if (m_nextDirOffset) {
+ TIFFSetSubDirectory(m_hTIFF, m_nextDirOffset);
+ }
+
+ uint32 width = 0;
+ uint32 height = 0;
+ TIFFGetField(m_hTIFF, TIFFTAG_IMAGEWIDTH, &width);
+ TIFFGetField(m_hTIFF, TIFFTAG_IMAGELENGTH, &height);
+ if (width == 0 || height == 0 || width > INT_MAX || height > INT_MAX) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Invalid image size");
+ return nullptr;
+ }
+
+ uint16 samplesPerPixel = 0;
+ if (!TIFFGetField(m_hTIFF, TIFFTAG_SAMPLESPERPIXEL, &samplesPerPixel)) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Missing SamplesPerPixel tag");
+ return nullptr;
+ }
+ if (samplesPerPixel == 0) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Invalid SamplesPerPixel value");
+ return nullptr;
+ }
+
+ uint16 bitsPerSample = 0;
+ if (!TIFFGetField(m_hTIFF, TIFFTAG_BITSPERSAMPLE, &bitsPerSample)) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Missing BitsPerSample tag");
+ return nullptr;
+ }
+
+ uint16 planarConfig = 0;
+ if (!TIFFGetField(m_hTIFF, TIFFTAG_PLANARCONFIG, &planarConfig)) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Missing PlanarConfig tag");
+ return nullptr;
+ }
+
+ uint16 sampleFormat = 0;
+ if (!TIFFGetField(m_hTIFF, TIFFTAG_SAMPLEFORMAT, &sampleFormat)) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Missing SampleFormat tag");
+ return nullptr;
+ }
+
+ TIFFDataType dt;
+ if (sampleFormat == SAMPLEFORMAT_INT && bitsPerSample == 16)
+ dt = TIFFDataType::Int16;
+ else if (sampleFormat == SAMPLEFORMAT_UINT && bitsPerSample == 16)
+ dt = TIFFDataType::UInt16;
+ else if (sampleFormat == SAMPLEFORMAT_INT && bitsPerSample == 32)
+ dt = TIFFDataType::Int32;
+ else if (sampleFormat == SAMPLEFORMAT_UINT && bitsPerSample == 32)
+ dt = TIFFDataType::UInt32;
+ else if (sampleFormat == SAMPLEFORMAT_IEEEFP && bitsPerSample == 32)
+ dt = TIFFDataType::Float32;
+ else if (sampleFormat == SAMPLEFORMAT_IEEEFP && bitsPerSample == 64)
+ dt = TIFFDataType::Float64;
+ else {
+ pj_log(
+ m_ctx, PJ_LOG_ERROR,
+ "Unsupported combination of SampleFormat and BitsPerSample values");
+ return nullptr;
+ }
+
+ uint16 photometric = PHOTOMETRIC_MINISBLACK;
+ if (!TIFFGetField(m_hTIFF, TIFFTAG_PHOTOMETRIC, &photometric))
+ photometric = PHOTOMETRIC_MINISBLACK;
+ if (photometric != PHOTOMETRIC_MINISBLACK) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Unsupported Photometric value");
+ return nullptr;
+ }
+
+ uint16 compression = COMPRESSION_NONE;
+ if (!TIFFGetField(m_hTIFF, TIFFTAG_COMPRESSION, &compression))
+ compression = COMPRESSION_NONE;
+
+ if (compression != COMPRESSION_NONE &&
+ !TIFFIsCODECConfigured(compression)) {
+ pj_log(m_ctx, PJ_LOG_ERROR,
+ "Cannot open TIFF file due to missing codec.");
+ return nullptr;
+ }
+ // We really don't want to try dealing with old-JPEG images
+ if (compression == COMPRESSION_OJPEG) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Unsupported compression method.");
+ return nullptr;
+ }
+
+ const auto blockSize = TIFFIsTiled(m_hTIFF) ? TIFFTileSize64(m_hTIFF)
+ : TIFFStripSize64(m_hTIFF);
+ if (blockSize == 0 || blockSize > 64 * 1024 * 2014) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Unsupported block size.");
+ return nullptr;
+ }
+
+ unsigned short count = 0;
+ unsigned short *geokeys = nullptr;
+ bool pixelIsArea = false;
+ if (!TIFFGetField(m_hTIFF, TIFFTAG_GEOKEYDIRECTORY, &count, &geokeys)) {
+ pj_log(m_ctx, PJ_LOG_DEBUG_MINOR, "No GeoKeys tag");
+ } else {
+ if (count < 4 || (count % 4) != 0) {
+ pj_log(m_ctx, PJ_LOG_ERROR,
+ "Wrong number of values in GeoKeys tag");
+ return nullptr;
+ }
+
+ if (geokeys[0] != 1) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Unsupported GeoTIFF major version");
+ return nullptr;
+ }
+ // We only know that we support GeoTIFF 1.0 and 1.1 at that time
+ if (geokeys[1] != 1 || geokeys[2] > 1) {
+ pj_log(m_ctx, PJ_LOG_DEBUG_MINOR,
+ "GeoTIFF %d.%d possibly not handled", geokeys[1],
+ geokeys[2]);
+ }
+
+ for (unsigned int i = 4; i + 3 < count; i += 4) {
+ constexpr unsigned short GTModelTypeGeoKey = 1024;
+ constexpr unsigned short ModelTypeGeographic = 2;
+
+ constexpr unsigned short GTRasterTypeGeoKey = 1025;
+ constexpr unsigned short RasterPixelIsArea = 1;
+ // constexpr unsigned short RasterPixelIsPoint = 2;
+
+ if (geokeys[i] == GTModelTypeGeoKey) {
+ if (geokeys[i + 3] != ModelTypeGeographic) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Only GTModelTypeGeoKey = "
+ "ModelTypeGeographic is "
+ "supported");
+ return nullptr;
+ }
+ } else if (geokeys[i] == GTRasterTypeGeoKey) {
+ if (geokeys[i + 3] == RasterPixelIsArea) {
+ pixelIsArea = true;
+ }
+ }
+ }
+ }
+
+ double hRes = 0;
+ double vRes = 0;
+ double westLon = 0;
+ double northLat = 0;
+
+ double *matrix = nullptr;
+ if (TIFFGetField(m_hTIFF, TIFFTAG_GEOTRANSMATRIX, &count, &matrix) &&
+ count == 16) {
+ // If using GDAL to produce a bottom-up georeferencing, it will produce
+ // a GeoTransformationMatrix, since negative values in GeoPixelScale
+ // have historically been implementation bugs.
+ if (matrix[1] != 0 || matrix[4] != 0) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Rotational terms not supported in "
+ "GeoTransformationMatrix tag");
+ return nullptr;
+ }
+
+ westLon = matrix[3];
+ hRes = matrix[0];
+ northLat = matrix[7];
+ vRes = -matrix[5]; // negation to simulate GeoPixelScale convention
+ } else {
+ double *geopixelscale = nullptr;
+ if (TIFFGetField(m_hTIFF, TIFFTAG_GEOPIXELSCALE, &count,
+ &geopixelscale) != 1) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "No GeoPixelScale tag");
+ return nullptr;
+ }
+ if (count != 3) {
+ pj_log(m_ctx, PJ_LOG_ERROR,
+ "Wrong number of values in GeoPixelScale tag");
+ return nullptr;
+ }
+ hRes = geopixelscale[0];
+ vRes = geopixelscale[1];
+
+ double *geotiepoints = nullptr;
+ if (TIFFGetField(m_hTIFF, TIFFTAG_GEOTIEPOINTS, &count,
+ &geotiepoints) != 1) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "No GeoTiePoints tag");
+ return nullptr;
+ }
+ if (count != 6) {
+ pj_log(m_ctx, PJ_LOG_ERROR,
+ "Wrong number of values in GeoTiePoints tag");
+ return nullptr;
+ }
+
+ westLon = geotiepoints[3] - geotiepoints[0] * hRes;
+ northLat = geotiepoints[4] + geotiepoints[1] * vRes;
+ }
+
+ if (pixelIsArea) {
+ westLon += 0.5 * hRes;
+ northLat -= 0.5 * vRes;
+ }
+
+ ExtentAndRes extent;
+ extent.westLon = westLon * DEG_TO_RAD;
+ extent.northLat = northLat * DEG_TO_RAD;
+ extent.resLon = hRes * DEG_TO_RAD;
+ extent.resLat = fabs(vRes) * DEG_TO_RAD;
+ extent.eastLon = (westLon + hRes * (width - 1)) * DEG_TO_RAD;
+ extent.southLat = (northLat - vRes * (height - 1)) * DEG_TO_RAD;
+
+ if (vRes < 0) {
+ std::swap(extent.northLat, extent.southLat);
+ }
+
+ if (!(fabs(extent.westLon) <= 4 * M_PI &&
+ fabs(extent.eastLon) <= 4 * M_PI &&
+ fabs(extent.northLat) <= M_PI + 1e-5 &&
+ fabs(extent.southLat) <= M_PI + 1e-5 &&
+ extent.westLon < extent.eastLon &&
+ extent.southLat < extent.northLat && extent.resLon > 1e-10 &&
+ extent.resLat > 1e-10)) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Inconsistent georeferencing for %s",
+ m_filename.c_str());
+ return nullptr;
+ }
+
+ auto ret = std::unique_ptr<GTiffGrid>(new GTiffGrid(
+ m_ctx, m_hTIFF, m_cache, m_fp.get(), m_ifdIdx, m_filename, width,
+ height, extent, dt, samplesPerPixel, planarConfig, vRes < 0));
+ m_ifdIdx++;
+ m_hasNextGrid = TIFFReadDirectory(m_hTIFF) != 0;
+ m_nextDirOffset = TIFFCurrentDirOffset(m_hTIFF);
+ return ret;
+}
+
+// ---------------------------------------------------------------------------
+
+class GTiffVGridShiftSet : public VerticalShiftGridSet {
+
+ std::unique_ptr<GTiffDataset> m_GTiffDataset;
+
+ GTiffVGridShiftSet(PJ_CONTEXT *ctx, std::unique_ptr<File> &&fp)
+ : m_GTiffDataset(new GTiffDataset(ctx, std::move(fp))) {}
+
+ public:
+ ~GTiffVGridShiftSet() override;
+
+ static std::unique_ptr<GTiffVGridShiftSet>
+ open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
+ const std::string &filename);
+
+ void reassign_context(PJ_CONTEXT *ctx) override {
+ VerticalShiftGridSet::reassign_context(ctx);
+ if (m_GTiffDataset) {
+ m_GTiffDataset->reassign_context(ctx);
+ }
+ }
+
+ bool reopen(PJ_CONTEXT *ctx) override {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Grid %s has changed. Re-loading it",
+ m_name.c_str());
+ m_grids.clear();
+ m_GTiffDataset.reset();
+ auto fp = FileManager::open_resource_file(ctx, m_name.c_str());
+ if (!fp) {
+ return false;
+ }
+ auto newGS = open(ctx, std::move(fp), m_name);
+ if (newGS) {
+ m_grids = std::move(newGS->m_grids);
+ m_GTiffDataset = std::move(newGS->m_GTiffDataset);
+ }
+ return !m_grids.empty();
+ }
+};
+
+#endif // TIFF_ENABLED
+
+// ---------------------------------------------------------------------------
+
+template <class GridType, class GenericGridType>
+static void
+insertIntoHierarchy(PJ_CONTEXT *ctx, std::unique_ptr<GridType> &&grid,
+ const std::string &gridName, const std::string &parentName,
+ std::vector<std::unique_ptr<GenericGridType>> &topGrids,
+ std::map<std::string, GridType *> &mapGrids) {
+ const auto &extent = grid->extentAndRes();
+
+ // If we have one or both of grid_name and parent_grid_name, try to use
+ // the names to recreate the hiearchy
+ if (!gridName.empty()) {
+ if (mapGrids.find(gridName) != mapGrids.end()) {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Several grids called %s found!",
+ gridName.c_str());
+ }
+ mapGrids[gridName] = grid.get();
+ }
+ bool gridInserted = false;
+ if (!parentName.empty()) {
+ auto iter = mapGrids.find(parentName);
+ if (iter == mapGrids.end()) {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "Grid %s refers to non-existing parent %s. "
+ "Using bounding-box method.",
+ gridName.c_str(), parentName.c_str());
+ } else {
+ if (iter->second->extentAndRes().contains(extent)) {
+ iter->second->m_children.emplace_back(std::move(grid));
+ gridInserted = true;
+ } else {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "Grid %s refers to parent %s, but its extent is "
+ "not included in it. Using bounding-box method.",
+ gridName.c_str(), parentName.c_str());
+ }
+ }
+ } else if (!gridName.empty()) {
+ topGrids.emplace_back(std::move(grid));
+ gridInserted = true;
+ }
+
+ // Fallback to analyzing spatial extents
+ if (!gridInserted) {
+ for (const auto &candidateParent : topGrids) {
+ const auto &candidateParentExtent = candidateParent->extentAndRes();
+ if (candidateParentExtent.contains(extent)) {
+ static_cast<GridType *>(candidateParent.get())
+ ->insertGrid(ctx, std::move(grid));
+ gridInserted = true;
+ break;
+ } else if (candidateParentExtent.intersects(extent)) {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "Partially intersecting grids found!");
+ }
+ }
+ if (!gridInserted) {
+ topGrids.emplace_back(std::move(grid));
+ }
+ }
+}
+
+#ifdef TIFF_ENABLED
+// ---------------------------------------------------------------------------
+
+class GTiffVGrid : public VerticalShiftGrid {
+ friend void insertIntoHierarchy<GTiffVGrid, VerticalShiftGrid>(
+ PJ_CONTEXT *ctx, std::unique_ptr<GTiffVGrid> &&grid,
+ const std::string &gridName, const std::string &parentName,
+ std::vector<std::unique_ptr<VerticalShiftGrid>> &topGrids,
+ std::map<std::string, GTiffVGrid *> &mapGrids);
+
+ std::unique_ptr<GTiffGrid> m_grid;
+ uint16 m_idxSample;
+
+ public:
+ GTiffVGrid(std::unique_ptr<GTiffGrid> &&grid, uint16 idxSample);
+
+ ~GTiffVGrid() override;
+
+ bool valueAt(int x, int y, float &out) const override {
+ return m_grid->valueAt(m_idxSample, x, y, out);
+ }
+
+ bool isNodata(float val, double /* multiplier */) const override {
+ return m_grid->isNodata(val);
+ }
+
+ void insertGrid(PJ_CONTEXT *ctx, std::unique_ptr<GTiffVGrid> &&subgrid);
+
+ void reassign_context(PJ_CONTEXT *ctx) override {
+ m_grid->reassign_context(ctx);
+ }
+
+ bool hasChanged() const override { return m_grid->hasChanged(); }
+};
+
+// ---------------------------------------------------------------------------
+
+GTiffVGridShiftSet::~GTiffVGridShiftSet() = default;
+
+// ---------------------------------------------------------------------------
+
+GTiffVGrid::GTiffVGrid(std::unique_ptr<GTiffGrid> &&grid, uint16 idxSample)
+ : VerticalShiftGrid(grid->name(), grid->width(), grid->height(),
+ grid->extentAndRes()),
+ m_grid(std::move(grid)), m_idxSample(idxSample) {}
+
+// ---------------------------------------------------------------------------
+
+GTiffVGrid::~GTiffVGrid() = default;
+
+// ---------------------------------------------------------------------------
+
+void GTiffVGrid::insertGrid(PJ_CONTEXT *ctx,
+ std::unique_ptr<GTiffVGrid> &&subgrid) {
+ bool gridInserted = false;
+ const auto &extent = subgrid->extentAndRes();
+ for (const auto &candidateParent : m_children) {
+ const auto &candidateParentExtent = candidateParent->extentAndRes();
+ if (candidateParentExtent.contains(extent)) {
+ static_cast<GTiffVGrid *>(candidateParent.get())
+ ->insertGrid(ctx, std::move(subgrid));
+ gridInserted = true;
+ break;
+ } else if (candidateParentExtent.intersects(extent)) {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "Partially intersecting grids found!");
+ }
+ }
+ if (!gridInserted) {
+ m_children.emplace_back(std::move(subgrid));
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<GTiffVGridShiftSet>
+GTiffVGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
+ const std::string &filename) {
+ auto set = std::unique_ptr<GTiffVGridShiftSet>(
+ new GTiffVGridShiftSet(ctx, std::move(fp)));
+ set->m_name = filename;
+ set->m_format = "gtiff";
+ if (!set->m_GTiffDataset->openTIFF(filename)) {
+ return nullptr;
+ }
+ uint16 idxSample = 0;
+
+ std::map<std::string, GTiffVGrid *> mapGrids;
+ for (int ifd = 0;; ++ifd) {
+ auto grid = set->m_GTiffDataset->nextGrid();
+ if (!grid) {
+ if (ifd == 0) {
+ return nullptr;
+ }
+ break;
+ }
+
+ const auto subfileType = grid->subfileType();
+ if (subfileType != 0 && subfileType != FILETYPE_PAGE) {
+ if (ifd == 0) {
+ pj_log(ctx, PJ_LOG_ERROR, "Invalid subfileType");
+ return nullptr;
+ } else {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "Ignoring IFD %d as it has a unsupported subfileType",
+ ifd);
+ continue;
+ }
+ }
+
+ // Identify the index of the geoid_undulation/vertical_offset
+ bool foundDescriptionForAtLeastOneSample = false;
+ bool foundDescriptionForShift = false;
+ for (int i = 0; i < static_cast<int>(grid->samplesPerPixel()); ++i) {
+ const auto desc = grid->metadataItem("DESCRIPTION", i);
+ if (!desc.empty()) {
+ foundDescriptionForAtLeastOneSample = true;
+ }
+ if (desc == "geoid_undulation" || desc == "vertical_offset") {
+ idxSample = static_cast<uint16>(i);
+ foundDescriptionForShift = true;
+ }
+ }
+
+ if (foundDescriptionForAtLeastOneSample) {
+ if (!foundDescriptionForShift) {
+ if (ifd > 0) {
+ // Assuming that extra IFD without our channel of interest
+ // can be ignored
+ // One could imagine to put the accuracy values in separate
+ // IFD for example
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "Ignoring IFD %d as it has no "
+ "geoid_undulation/vertical_offset channel",
+ ifd);
+ continue;
+ } else {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "IFD 0 has channel descriptions, but no "
+ "geoid_undulation/vertical_offset channel");
+ return nullptr;
+ }
+ }
+ }
+
+ if (idxSample >= grid->samplesPerPixel()) {
+ pj_log(ctx, PJ_LOG_ERROR, "Invalid sample index");
+ return nullptr;
+ }
+
+ const std::string gridName = grid->metadataItem("grid_name");
+ const std::string parentName = grid->metadataItem("parent_grid_name");
+
+ auto vgrid =
+ internal::make_unique<GTiffVGrid>(std::move(grid), idxSample);
+
+ insertIntoHierarchy(ctx, std::move(vgrid), gridName, parentName,
+ set->m_grids, mapGrids);
+ }
+ return set;
+}
+#endif // TIFF_ENABLED
+
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<VerticalShiftGridSet>
+VerticalShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) {
+ if (filename == "null") {
+ auto set =
+ std::unique_ptr<VerticalShiftGridSet>(new VerticalShiftGridSet());
+ set->m_name = filename;
+ set->m_format = "null";
+ set->m_grids.push_back(std::unique_ptr<NullVerticalShiftGrid>(
+ new NullVerticalShiftGrid()));
+ return set;
+ }
+
+ auto fp = FileManager::open_resource_file(ctx, filename.c_str());
+ if (!fp) {
+ return nullptr;
+ }
+ const auto actualName(fp->name());
+ if (ends_with(actualName, "gtx") || ends_with(actualName, "GTX")) {
+ auto grid = GTXVerticalShiftGrid::open(ctx, std::move(fp), actualName);
+ if (!grid) {
+ return nullptr;
+ }
+ auto set =
+ std::unique_ptr<VerticalShiftGridSet>(new VerticalShiftGridSet());
+ set->m_name = actualName;
+ set->m_format = "gtx";
+ set->m_grids.push_back(std::unique_ptr<VerticalShiftGrid>(grid));
+ return set;
+ }
+
+ /* -------------------------------------------------------------------- */
+ /* Load a header, to determine the file type. */
+ /* -------------------------------------------------------------------- */
+ unsigned char header[4];
+ size_t header_size = fp->read(header, sizeof(header));
+ if (header_size != sizeof(header)) {
+ return nullptr;
+ }
+ fp->seek(0);
+
+ if (IsTIFF(header_size, header)) {
+#ifdef TIFF_ENABLED
+ auto set = GTiffVGridShiftSet::open(ctx, std::move(fp), actualName);
+ if (!set)
+ pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ return set;
+#else
+ pj_log(ctx, PJ_LOG_ERROR,
+ "TIFF grid, but TIFF support disabled in this build");
+ return nullptr;
+#endif
+ }
+
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Unrecognized vertical grid format");
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+bool VerticalShiftGridSet::reopen(PJ_CONTEXT *ctx) {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Grid %s has changed. Re-loading it",
+ m_name.c_str());
+ auto newGS = open(ctx, m_name);
+ m_grids.clear();
+ if (newGS) {
+ m_grids = std::move(newGS->m_grids);
+ }
+ return !m_grids.empty();
+}
+
+// ---------------------------------------------------------------------------
+
+const VerticalShiftGrid *VerticalShiftGrid::gridAt(double lon,
+ double lat) const {
+ for (const auto &child : m_children) {
+ const auto &extentChild = child->extentAndRes();
+ if ((extentChild.fullWorldLongitude() ||
+ (lon >= extentChild.westLon && lon <= extentChild.eastLon)) &&
+ lat >= extentChild.southLat && lat <= extentChild.northLat) {
+ return child->gridAt(lon, lat);
+ }
+ }
+ return this;
+}
+// ---------------------------------------------------------------------------
+
+const VerticalShiftGrid *VerticalShiftGridSet::gridAt(double lon,
+ double lat) const {
+ for (const auto &grid : m_grids) {
+ if (dynamic_cast<NullVerticalShiftGrid *>(grid.get())) {
+ return grid.get();
+ }
+ const auto &extent = grid->extentAndRes();
+ if ((extent.fullWorldLongitude() ||
+ (lon >= extent.westLon && lon <= extent.eastLon)) &&
+ lat >= extent.southLat && lat <= extent.northLat) {
+ return grid->gridAt(lon, lat);
+ }
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+void VerticalShiftGridSet::reassign_context(PJ_CONTEXT *ctx) {
+ for (const auto &grid : m_grids) {
+ grid->reassign_context(ctx);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+HorizontalShiftGrid::HorizontalShiftGrid(const std::string &nameIn, int widthIn,
+ int heightIn,
+ const ExtentAndRes &extentIn)
+ : Grid(nameIn, widthIn, heightIn, extentIn) {}
+
+// ---------------------------------------------------------------------------
+
+HorizontalShiftGrid::~HorizontalShiftGrid() = default;
+
+// ---------------------------------------------------------------------------
+
+HorizontalShiftGridSet::HorizontalShiftGridSet() = default;
+
+// ---------------------------------------------------------------------------
+
+HorizontalShiftGridSet::~HorizontalShiftGridSet() = default;
+
+// ---------------------------------------------------------------------------
+
+class NullHorizontalShiftGrid : public HorizontalShiftGrid {
+
+ public:
+ NullHorizontalShiftGrid()
+ : HorizontalShiftGrid("null", 3, 3, globalExtent()) {}
+
+ bool isNullGrid() const override { return true; }
+
+ bool valueAt(int, int, bool, float &lonShift,
+ float &latShift) const override;
+
+ void reassign_context(PJ_CONTEXT *) override {}
+
+ bool hasChanged() const override { return false; }
+};
+
+// ---------------------------------------------------------------------------
+
+bool NullHorizontalShiftGrid::valueAt(int, int, bool, float &lonShift,
+ float &latShift) const {
+ lonShift = 0.0f;
+ latShift = 0.0f;
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+static double to_double(const void *data) {
+ double d;
+ memcpy(&d, data, sizeof(d));
+ return d;
+}
+
+// ---------------------------------------------------------------------------
+
+class NTv1Grid : public HorizontalShiftGrid {
+ PJ_CONTEXT *m_ctx;
+ std::unique_ptr<File> m_fp;
+
+ NTv1Grid(const NTv1Grid &) = delete;
+ NTv1Grid &operator=(const NTv1Grid &) = delete;
+
+ public:
+ explicit NTv1Grid(PJ_CONTEXT *ctx, std::unique_ptr<File> &&fp,
+ const std::string &nameIn, int widthIn, int heightIn,
+ const ExtentAndRes &extentIn)
+ : HorizontalShiftGrid(nameIn, widthIn, heightIn, extentIn), m_ctx(ctx),
+ m_fp(std::move(fp)) {}
+
+ ~NTv1Grid() override;
+
+ bool valueAt(int, int, bool, float &lonShift,
+ float &latShift) const override;
+
+ static NTv1Grid *open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
+ const std::string &filename);
+
+ void reassign_context(PJ_CONTEXT *ctx) override {
+ m_ctx = ctx;
+ m_fp->reassign_context(ctx);
+ }
+
+ bool hasChanged() const override { return m_fp->hasChanged(); }
+};
+
+// ---------------------------------------------------------------------------
+
+NTv1Grid::~NTv1Grid() = default;
+
+// ---------------------------------------------------------------------------
+
+NTv1Grid *NTv1Grid::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
+ const std::string &filename) {
+ unsigned char header[192];
+
+ /* -------------------------------------------------------------------- */
+ /* Read the header. */
+ /* -------------------------------------------------------------------- */
+ if (fp->read(header, sizeof(header)) != sizeof(header)) {
+ pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ return nullptr;
+ }
+
+ /* -------------------------------------------------------------------- */
+ /* Regularize fields of interest. */
+ /* -------------------------------------------------------------------- */
+ if (IS_LSB) {
+ swap_words(header + 8, sizeof(int), 1);
+ swap_words(header + 24, sizeof(double), 1);
+ swap_words(header + 40, sizeof(double), 1);
+ swap_words(header + 56, sizeof(double), 1);
+ swap_words(header + 72, sizeof(double), 1);
+ swap_words(header + 88, sizeof(double), 1);
+ swap_words(header + 104, sizeof(double), 1);
+ }
+
+ if (*((int *)(header + 8)) != 12) {
+ pj_log(ctx, PJ_LOG_ERROR,
+ "NTv1 grid shift file has wrong record count, corrupt?");
+ pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ return nullptr;
+ }
+
+ ExtentAndRes extent;
+ extent.westLon = -to_double(header + 72) * DEG_TO_RAD;
+ extent.southLat = to_double(header + 24) * DEG_TO_RAD;
+ extent.eastLon = -to_double(header + 56) * DEG_TO_RAD;
+ extent.northLat = to_double(header + 40) * DEG_TO_RAD;
+ extent.resLon = to_double(header + 104) * DEG_TO_RAD;
+ extent.resLat = to_double(header + 88) * DEG_TO_RAD;
+ if (!(fabs(extent.westLon) <= 4 * M_PI &&
+ fabs(extent.eastLon) <= 4 * M_PI &&
+ fabs(extent.northLat) <= M_PI + 1e-5 &&
+ fabs(extent.southLat) <= M_PI + 1e-5 &&
+ extent.westLon < extent.eastLon &&
+ extent.southLat < extent.northLat && extent.resLon > 1e-10 &&
+ extent.resLat > 1e-10)) {
+ pj_log(ctx, PJ_LOG_ERROR, "Inconsistent georeferencing for %s",
+ filename.c_str());
+ pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ return nullptr;
+ }
+ const int columns = static_cast<int>(
+ fabs((extent.eastLon - extent.westLon) / extent.resLon + 0.5) + 1);
+ const int rows = static_cast<int>(
+ fabs((extent.northLat - extent.southLat) / extent.resLat + 0.5) + 1);
+
+ return new NTv1Grid(ctx, std::move(fp), filename, columns, rows, extent);
+}
+
+// ---------------------------------------------------------------------------
+
+bool NTv1Grid::valueAt(int x, int y, bool compensateNTConvention,
+ float &lonShift, float &latShift) const {
+ assert(x >= 0 && y >= 0 && x < m_width && y < m_height);
+
+ double two_doubles[2];
+ // NTv1 is organized from east to west !
+ m_fp->seek(192 + 2 * sizeof(double) * (y * m_width + m_width - 1 - x));
+ if (m_fp->read(&two_doubles[0], sizeof(two_doubles)) !=
+ sizeof(two_doubles)) {
+ pj_ctx_set_errno(m_ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ return false;
+ }
+ if (IS_LSB) {
+ swap_words(&two_doubles[0], sizeof(double), 2);
+ }
+ /* convert seconds to radians */
+ latShift = static_cast<float>(two_doubles[0] * ((M_PI / 180.0) / 3600.0));
+ // west longitude positive convention !
+ lonShift = (compensateNTConvention ? -1 : 1) *
+ static_cast<float>(two_doubles[1] * ((M_PI / 180.0) / 3600.0));
+
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+class CTable2Grid : public HorizontalShiftGrid {
+ PJ_CONTEXT *m_ctx;
+ std::unique_ptr<File> m_fp;
+
+ CTable2Grid(const CTable2Grid &) = delete;
+ CTable2Grid &operator=(const CTable2Grid &) = delete;
+
+ public:
+ CTable2Grid(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
+ const std::string &nameIn, int widthIn, int heightIn,
+ const ExtentAndRes &extentIn)
+ : HorizontalShiftGrid(nameIn, widthIn, heightIn, extentIn), m_ctx(ctx),
+ m_fp(std::move(fp)) {}
+
+ ~CTable2Grid() override;
+
+ bool valueAt(int, int, bool, float &lonShift,
+ float &latShift) const override;
+
+ static CTable2Grid *open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
+ const std::string &filename);
+
+ void reassign_context(PJ_CONTEXT *ctx) override {
+ m_ctx = ctx;
+ m_fp->reassign_context(ctx);
+ }
+
+ bool hasChanged() const override { return m_fp->hasChanged(); }
+};
+
+// ---------------------------------------------------------------------------
+
+CTable2Grid::~CTable2Grid() = default;
+
+// ---------------------------------------------------------------------------
+
+CTable2Grid *CTable2Grid::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
+ const std::string &filename) {
+ unsigned char header[160];
+
+ /* -------------------------------------------------------------------- */
+ /* Read the header. */
+ /* -------------------------------------------------------------------- */
+ if (fp->read(header, sizeof(header)) != sizeof(header)) {
+ pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ return nullptr;
+ }
+
+ /* -------------------------------------------------------------------- */
+ /* Regularize fields of interest. */
+ /* -------------------------------------------------------------------- */
+ if (!IS_LSB) {
+ swap_words(header + 96, sizeof(double), 4);
+ swap_words(header + 128, sizeof(int), 2);
+ }
+
+ ExtentAndRes extent;
+ static_assert(sizeof(extent.westLon) == 8, "wrong sizeof");
+ static_assert(sizeof(extent.southLat) == 8, "wrong sizeof");
+ static_assert(sizeof(extent.resLon) == 8, "wrong sizeof");
+ static_assert(sizeof(extent.resLat) == 8, "wrong sizeof");
+ memcpy(&extent.westLon, header + 96, 8);
+ memcpy(&extent.southLat, header + 104, 8);
+ memcpy(&extent.resLon, header + 112, 8);
+ memcpy(&extent.resLat, header + 120, 8);
+ if (!(fabs(extent.westLon) <= 4 * M_PI &&
+ fabs(extent.southLat) <= M_PI + 1e-5 && extent.resLon > 1e-10 &&
+ extent.resLat > 1e-10)) {
+ pj_log(ctx, PJ_LOG_ERROR, "Inconsistent georeferencing for %s",
+ filename.c_str());
+ pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ return nullptr;
+ }
+ int width;
+ int height;
+ memcpy(&width, header + 128, 4);
+ memcpy(&height, header + 132, 4);
+ if (width <= 0 || height <= 0) {
+ pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ return nullptr;
+ }
+ extent.eastLon = extent.westLon + (width - 1) * extent.resLon;
+ extent.northLat = extent.southLat + (height - 1) * extent.resLon;
+
+ return new CTable2Grid(ctx, std::move(fp), filename, width, height, extent);
+}
+
+// ---------------------------------------------------------------------------
+
+bool CTable2Grid::valueAt(int x, int y, bool compensateNTConvention,
+ float &lonShift, float &latShift) const {
+ assert(x >= 0 && y >= 0 && x < m_width && y < m_height);
+
+ float two_floats[2];
+ m_fp->seek(160 + 2 * sizeof(float) * (y * m_width + x));
+ if (m_fp->read(&two_floats[0], sizeof(two_floats)) != sizeof(two_floats)) {
+ pj_ctx_set_errno(m_ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ return false;
+ }
+ if (!IS_LSB) {
+ swap_words(&two_floats[0], sizeof(float), 2);
+ }
+
+ latShift = two_floats[1];
+ // west longitude positive convention !
+ lonShift = (compensateNTConvention ? -1 : 1) * two_floats[0];
+
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+class NTv2GridSet : public HorizontalShiftGridSet {
+ std::unique_ptr<File> m_fp;
+
+ NTv2GridSet(const NTv2GridSet &) = delete;
+ NTv2GridSet &operator=(const NTv2GridSet &) = delete;
+
+ explicit NTv2GridSet(std::unique_ptr<File> &&fp) : m_fp(std::move(fp)) {}
+
+ public:
+ ~NTv2GridSet() override;
+
+ static std::unique_ptr<NTv2GridSet> open(PJ_CONTEXT *ctx,
+ std::unique_ptr<File> fp,
+ const std::string &filename);
+
+ void reassign_context(PJ_CONTEXT *ctx) override {
+ HorizontalShiftGridSet::reassign_context(ctx);
+ m_fp->reassign_context(ctx);
+ }
+};
+
+// ---------------------------------------------------------------------------
+
+class NTv2Grid : public HorizontalShiftGrid {
+ friend class NTv2GridSet;
+
+ std::string m_name;
+ PJ_CONTEXT *m_ctx; // owned by the parent NTv2GridSet
+ File *m_fp; // owned by the parent NTv2GridSet
+ unsigned long long m_offset;
+ bool m_mustSwap;
+
+ NTv2Grid(const NTv2Grid &) = delete;
+ NTv2Grid &operator=(const NTv2Grid &) = delete;
+
+ public:
+ NTv2Grid(const std::string &nameIn, PJ_CONTEXT *ctx, File *fp,
+ unsigned long long offsetIn, bool mustSwapIn, int widthIn,
+ int heightIn, const ExtentAndRes &extentIn)
+ : HorizontalShiftGrid(nameIn, widthIn, heightIn, extentIn),
+ m_name(nameIn), m_ctx(ctx), m_fp(fp), m_offset(offsetIn),
+ m_mustSwap(mustSwapIn) {}
+
+ bool valueAt(int, int, bool, float &lonShift,
+ float &latShift) const override;
+
+ void reassign_context(PJ_CONTEXT *ctx) override {
+ m_ctx = ctx;
+ m_fp->reassign_context(ctx);
+ }
+
+ bool hasChanged() const override { return m_fp->hasChanged(); }
+};
+
+// ---------------------------------------------------------------------------
+
+bool NTv2Grid::valueAt(int x, int y, bool compensateNTConvention,
+ float &lonShift, float &latShift) const {
+ assert(x >= 0 && y >= 0 && x < m_width && y < m_height);
+
+ float two_float[2];
+ // NTv2 is organized from east to west !
+ // there are 4 components: lat shift, lon shift, lat error, lon error
+ m_fp->seek(
+ m_offset +
+ 4 * sizeof(float) *
+ (static_cast<unsigned long long>(y) * m_width + m_width - 1 - x));
+ if (m_fp->read(&two_float[0], sizeof(two_float)) != sizeof(two_float)) {
+ pj_ctx_set_errno(m_ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ return false;
+ }
+ if (m_mustSwap) {
+ swap_words(&two_float[0], sizeof(float), 2);
+ }
+ /* convert seconds to radians */
+ latShift = static_cast<float>(two_float[0] * ((M_PI / 180.0) / 3600.0));
+ // west longitude positive convention !
+ lonShift = (compensateNTConvention ? -1 : 1) *
+ static_cast<float>(two_float[1] * ((M_PI / 180.0) / 3600.0));
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+NTv2GridSet::~NTv2GridSet() = default;
+
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<NTv2GridSet> NTv2GridSet::open(PJ_CONTEXT *ctx,
+ std::unique_ptr<File> fp,
+ const std::string &filename) {
+ File *fpRaw = fp.get();
+ auto set = std::unique_ptr<NTv2GridSet>(new NTv2GridSet(std::move(fp)));
+ set->m_name = filename;
+ set->m_format = "ntv2";
+
+ char header[11 * 16];
+
+ /* -------------------------------------------------------------------- */
+ /* Read the header. */
+ /* -------------------------------------------------------------------- */
+ if (fpRaw->read(header, sizeof(header)) != sizeof(header)) {
+ pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ return nullptr;
+ }
+
+ constexpr int OFFSET_GS_TYPE = 56;
+ if (memcmp(header + OFFSET_GS_TYPE, "SECONDS", 7) != 0) {
+ pj_log(ctx, PJ_LOG_ERROR, "Only GS_TYPE=SECONDS is supported");
+ pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ return nullptr;
+ }
+
+ const bool must_swap = (header[8] == 11) ? !IS_LSB : IS_LSB;
+ constexpr int OFFSET_NUM_SUBFILES = 8 + 32;
+ if (must_swap) {
+ // swap_words( header+8, 4, 1 );
+ // swap_words( header+8+16, 4, 1 );
+ swap_words(header + OFFSET_NUM_SUBFILES, 4, 1);
+ // swap_words( header+8+7*16, 8, 1 );
+ // swap_words( header+8+8*16, 8, 1 );
+ // swap_words( header+8+9*16, 8, 1 );
+ // swap_words( header+8+10*16, 8, 1 );
+ }
+
+ /* -------------------------------------------------------------------- */
+ /* Get the subfile count out ... all we really use for now. */
+ /* -------------------------------------------------------------------- */
+ unsigned int num_subfiles;
+ memcpy(&num_subfiles, header + OFFSET_NUM_SUBFILES, 4);
+
+ std::map<std::string, NTv2Grid *> mapGrids;
+
+ /* ==================================================================== */
+ /* Step through the subfiles, creating a grid for each. */
+ /* ==================================================================== */
+ for (unsigned subfile = 0; subfile < num_subfiles; subfile++) {
+ // Read header
+ if (fpRaw->read(header, sizeof(header)) != sizeof(header)) {
+ pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ return nullptr;
+ }
+
+ if (strncmp(header, "SUB_NAME", 8) != 0) {
+ pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ return nullptr;
+ }
+
+ // Byte swap interesting fields if needed.
+ constexpr int OFFSET_GS_COUNT = 8 + 16 * 10;
+ constexpr int OFFSET_SOUTH_LAT = 8 + 16 * 4;
+ if (must_swap) {
+ // 6 double values: southLat, northLat, eastLon, westLon, resLat,
+ // resLon
+ swap_words(header + OFFSET_SOUTH_LAT, sizeof(double), 6);
+ swap_words(header + OFFSET_GS_COUNT, sizeof(int), 1);
+ }
+
+ std::string gridName;
+ gridName.append(header + 8, 8);
+
+ ExtentAndRes extent;
+ extent.southLat = to_double(header + OFFSET_SOUTH_LAT) * DEG_TO_RAD /
+ 3600.0; /* S_LAT */
+ extent.northLat = to_double(header + OFFSET_SOUTH_LAT + 16) *
+ DEG_TO_RAD / 3600.0; /* N_LAT */
+ extent.eastLon = -to_double(header + OFFSET_SOUTH_LAT + 16 * 2) *
+ DEG_TO_RAD / 3600.0; /* E_LONG */
+ extent.westLon = -to_double(header + OFFSET_SOUTH_LAT + 16 * 3) *
+ DEG_TO_RAD / 3600.0; /* W_LONG */
+ extent.resLat =
+ to_double(header + OFFSET_SOUTH_LAT + 16 * 4) * DEG_TO_RAD / 3600.0;
+ extent.resLon =
+ to_double(header + OFFSET_SOUTH_LAT + 16 * 5) * DEG_TO_RAD / 3600.0;
+
+ if (!(fabs(extent.westLon) <= 4 * M_PI &&
+ fabs(extent.eastLon) <= 4 * M_PI &&
+ fabs(extent.northLat) <= M_PI + 1e-5 &&
+ fabs(extent.southLat) <= M_PI + 1e-5 &&
+ extent.westLon < extent.eastLon &&
+ extent.southLat < extent.northLat && extent.resLon > 1e-10 &&
+ extent.resLat > 1e-10)) {
+ pj_log(ctx, PJ_LOG_ERROR, "Inconsistent georeferencing for %s",
+ filename.c_str());
+ pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ return nullptr;
+ }
+ const int columns = static_cast<int>(
+ fabs((extent.eastLon - extent.westLon) / extent.resLon + 0.5) + 1);
+ const int rows = static_cast<int>(
+ fabs((extent.northLat - extent.southLat) / extent.resLat + 0.5) +
+ 1);
+
+ pj_log(ctx, PJ_LOG_DEBUG_MINOR,
+ "NTv2 %s %dx%d: LL=(%.9g,%.9g) UR=(%.9g,%.9g)", gridName.c_str(),
+ columns, rows, extent.westLon * RAD_TO_DEG,
+ extent.southLat * RAD_TO_DEG, extent.eastLon * RAD_TO_DEG,
+ extent.northLat * RAD_TO_DEG);
+
+ unsigned int gs_count;
+ memcpy(&gs_count, header + OFFSET_GS_COUNT, 4);
+ if (gs_count / columns != static_cast<unsigned>(rows)) {
+ pj_log(ctx, PJ_LOG_ERROR,
+ "GS_COUNT(%u) does not match expected cells (%dx%d)",
+ gs_count, columns, rows);
+ pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ return nullptr;
+ }
+
+ const auto offset = fpRaw->tell();
+ auto grid = std::unique_ptr<NTv2Grid>(
+ new NTv2Grid(filename + ", " + gridName, ctx, fpRaw, offset,
+ must_swap, columns, rows, extent));
+ std::string parentName;
+ parentName.assign(header + 24, 8);
+ auto iter = mapGrids.find(parentName);
+ auto gridPtr = grid.get();
+ if (iter == mapGrids.end()) {
+ set->m_grids.emplace_back(std::move(grid));
+ } else {
+ iter->second->m_children.emplace_back(std::move(grid));
+ }
+ mapGrids[gridName] = gridPtr;
+
+ // Skip grid data. 4 components of size float
+ fpRaw->seek(static_cast<unsigned long long>(gs_count) * 4 * 4,
+ SEEK_CUR);
+ }
+ return set;
+}
+
+#ifdef TIFF_ENABLED
+
+// ---------------------------------------------------------------------------
+
+class GTiffHGridShiftSet : public HorizontalShiftGridSet {
+
+ std::unique_ptr<GTiffDataset> m_GTiffDataset;
+
+ GTiffHGridShiftSet(PJ_CONTEXT *ctx, std::unique_ptr<File> &&fp)
+ : m_GTiffDataset(new GTiffDataset(ctx, std::move(fp))) {}
+
+ public:
+ ~GTiffHGridShiftSet() override;
+
+ static std::unique_ptr<GTiffHGridShiftSet>
+ open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
+ const std::string &filename);
+
+ void reassign_context(PJ_CONTEXT *ctx) override {
+ HorizontalShiftGridSet::reassign_context(ctx);
+ if (m_GTiffDataset) {
+ m_GTiffDataset->reassign_context(ctx);
+ }
+ }
+
+ bool reopen(PJ_CONTEXT *ctx) override {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Grid %s has changed. Re-loading it",
+ m_name.c_str());
+ m_grids.clear();
+ m_GTiffDataset.reset();
+ auto fp = FileManager::open_resource_file(ctx, m_name.c_str());
+ if (!fp) {
+ return false;
+ }
+ auto newGS = open(ctx, std::move(fp), m_name);
+ if (newGS) {
+ m_grids = std::move(newGS->m_grids);
+ m_GTiffDataset = std::move(newGS->m_GTiffDataset);
+ }
+ return !m_grids.empty();
+ }
+};
+
+// ---------------------------------------------------------------------------
+
+class GTiffHGrid : public HorizontalShiftGrid {
+ friend void insertIntoHierarchy<GTiffHGrid, HorizontalShiftGrid>(
+ PJ_CONTEXT *ctx, std::unique_ptr<GTiffHGrid> &&grid,
+ const std::string &gridName, const std::string &parentName,
+ std::vector<std::unique_ptr<HorizontalShiftGrid>> &topGrids,
+ std::map<std::string, GTiffHGrid *> &mapGrids);
+
+ std::unique_ptr<GTiffGrid> m_grid;
+ uint16 m_idxLatShift;
+ uint16 m_idxLonShift;
+ double m_convFactorToRadian;
+ bool m_positiveEast;
+
+ public:
+ GTiffHGrid(std::unique_ptr<GTiffGrid> &&grid, uint16 idxLatShift,
+ uint16 idxLonShift, double convFactorToRadian,
+ bool positiveEast);
+
+ ~GTiffHGrid() override;
+
+ bool valueAt(int x, int y, bool, float &lonShift,
+ float &latShift) const override;
+
+ void insertGrid(PJ_CONTEXT *ctx, std::unique_ptr<GTiffHGrid> &&subgrid);
+
+ void reassign_context(PJ_CONTEXT *ctx) override {
+ m_grid->reassign_context(ctx);
+ }
+
+ bool hasChanged() const override { return m_grid->hasChanged(); }
+};
+
+// ---------------------------------------------------------------------------
+
+GTiffHGridShiftSet::~GTiffHGridShiftSet() = default;
+
+// ---------------------------------------------------------------------------
+
+GTiffHGrid::GTiffHGrid(std::unique_ptr<GTiffGrid> &&grid, uint16 idxLatShift,
+ uint16 idxLonShift, double convFactorToRadian,
+ bool positiveEast)
+ : HorizontalShiftGrid(grid->name(), grid->width(), grid->height(),
+ grid->extentAndRes()),
+ m_grid(std::move(grid)), m_idxLatShift(idxLatShift),
+ m_idxLonShift(idxLonShift), m_convFactorToRadian(convFactorToRadian),
+ m_positiveEast(positiveEast) {}
+
+// ---------------------------------------------------------------------------
+
+GTiffHGrid::~GTiffHGrid() = default;
+
+// ---------------------------------------------------------------------------
+
+bool GTiffHGrid::valueAt(int x, int y, bool, float &lonShift,
+ float &latShift) const {
+ if (!m_grid->valueAt(m_idxLatShift, x, y, latShift) ||
+ !m_grid->valueAt(m_idxLonShift, x, y, lonShift)) {
+ return false;
+ }
+ // From arc-seconds to radians
+ latShift = static_cast<float>(latShift * m_convFactorToRadian);
+ lonShift = static_cast<float>(lonShift * m_convFactorToRadian);
+ if (!m_positiveEast) {
+ lonShift = -lonShift;
+ }
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+void GTiffHGrid::insertGrid(PJ_CONTEXT *ctx,
+ std::unique_ptr<GTiffHGrid> &&subgrid) {
+ bool gridInserted = false;
+ const auto &extent = subgrid->extentAndRes();
+ for (const auto &candidateParent : m_children) {
+ const auto &candidateParentExtent = candidateParent->extentAndRes();
+ if (candidateParentExtent.contains(extent)) {
+ static_cast<GTiffHGrid *>(candidateParent.get())
+ ->insertGrid(ctx, std::move(subgrid));
+ gridInserted = true;
+ break;
+ } else if (candidateParentExtent.intersects(extent)) {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "Partially intersecting grids found!");
+ }
+ }
+ if (!gridInserted) {
+ m_children.emplace_back(std::move(subgrid));
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<GTiffHGridShiftSet>
+GTiffHGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
+ const std::string &filename) {
+ auto set = std::unique_ptr<GTiffHGridShiftSet>(
+ new GTiffHGridShiftSet(ctx, std::move(fp)));
+ set->m_name = filename;
+ set->m_format = "gtiff";
+ if (!set->m_GTiffDataset->openTIFF(filename)) {
+ return nullptr;
+ }
+
+ // Defaults inspired from NTv2
+ uint16 idxLatShift = 0;
+ uint16 idxLonShift = 1;
+ constexpr double ARC_SECOND_TO_RADIAN = (M_PI / 180.0) / 3600.0;
+ double convFactorToRadian = ARC_SECOND_TO_RADIAN;
+ bool positiveEast = true;
+
+ std::map<std::string, GTiffHGrid *> mapGrids;
+ for (int ifd = 0;; ++ifd) {
+ auto grid = set->m_GTiffDataset->nextGrid();
+ if (!grid) {
+ if (ifd == 0) {
+ return nullptr;
+ }
+ break;
+ }
+
+ const auto subfileType = grid->subfileType();
+ if (subfileType != 0 && subfileType != FILETYPE_PAGE) {
+ if (ifd == 0) {
+ pj_log(ctx, PJ_LOG_ERROR, "Invalid subfileType");
+ return nullptr;
+ } else {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "Ignoring IFD %d as it has a unsupported subfileType",
+ ifd);
+ continue;
+ }
+ }
+
+ if (grid->samplesPerPixel() < 2) {
+ if (ifd == 0) {
+ pj_log(ctx, PJ_LOG_ERROR,
+ "At least 2 samples per pixel needed");
+ return nullptr;
+ } else {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "Ignoring IFD %d as it has not at least 2 samples", ifd);
+ continue;
+ }
+ }
+
+ // Identify the index of the latitude and longitude offset channels
+ bool foundDescriptionForAtLeastOneSample = false;
+ bool foundDescriptionForLatOffset = false;
+ bool foundDescriptionForLonOffset = false;
+ for (int i = 0; i < static_cast<int>(grid->samplesPerPixel()); ++i) {
+ const auto desc = grid->metadataItem("DESCRIPTION", i);
+ if (!desc.empty()) {
+ foundDescriptionForAtLeastOneSample = true;
+ }
+ if (desc == "latitude_offset") {
+ idxLatShift = static_cast<uint16>(i);
+ foundDescriptionForLatOffset = true;
+ } else if (desc == "longitude_offset") {
+ idxLonShift = static_cast<uint16>(i);
+ foundDescriptionForLonOffset = true;
+ }
+ }
+
+ if (foundDescriptionForAtLeastOneSample) {
+ if (!foundDescriptionForLonOffset &&
+ !foundDescriptionForLatOffset) {
+ if (ifd > 0) {
+ // Assuming that extra IFD without
+ // longitude_offset/latitude_offset can be ignored
+ // One could imagine to put the accuracy values in separate
+ // IFD for example
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "Ignoring IFD %d as it has no "
+ "longitude_offset/latitude_offset channel",
+ ifd);
+ continue;
+ } else {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "IFD 0 has channel descriptions, but no "
+ "longitude_offset/latitude_offset channel");
+ return nullptr;
+ }
+ }
+ }
+ if (foundDescriptionForLatOffset && !foundDescriptionForLonOffset) {
+ pj_log(ctx, PJ_LOG_ERROR,
+ "Found latitude_offset channel, but not longitude_offset");
+ return nullptr;
+ } else if (foundDescriptionForLonOffset &&
+ !foundDescriptionForLatOffset) {
+ pj_log(ctx, PJ_LOG_ERROR,
+ "Found longitude_offset channel, but not latitude_offset");
+ return nullptr;
+ }
+
+ if (idxLatShift >= grid->samplesPerPixel() ||
+ idxLonShift >= grid->samplesPerPixel()) {
+ pj_log(ctx, PJ_LOG_ERROR, "Invalid sample index");
+ return nullptr;
+ }
+
+ if (foundDescriptionForLonOffset) {
+ const std::string positiveValue =
+ grid->metadataItem("positive_value", idxLonShift);
+ if (!positiveValue.empty()) {
+ if (positiveValue == "west") {
+ positiveEast = false;
+ } else if (positiveValue == "east") {
+ positiveEast = true;
+ } else {
+ pj_log(ctx, PJ_LOG_ERROR,
+ "Unsupported value %s for 'positive_value'",
+ positiveValue.c_str());
+ return nullptr;
+ }
+ }
+ }
+
+ // Identify their unit
+ {
+ const auto unitLatShift =
+ grid->metadataItem("UNITTYPE", idxLatShift);
+ const auto unitLonShift =
+ grid->metadataItem("UNITTYPE", idxLonShift);
+ if (unitLatShift != unitLonShift) {
+ pj_log(ctx, PJ_LOG_ERROR,
+ "Different unit for longitude and latitude offset");
+ return nullptr;
+ }
+ if (!unitLatShift.empty()) {
+ if (unitLatShift == "arc-second") {
+ convFactorToRadian = ARC_SECOND_TO_RADIAN;
+ } else if (unitLatShift == "radian") {
+ convFactorToRadian = 1.0;
+ } else if (unitLatShift == "degree") {
+ convFactorToRadian = M_PI / 180.0;
+ } else {
+ pj_log(ctx, PJ_LOG_ERROR, "Unsupported unit %s",
+ unitLatShift.c_str());
+ return nullptr;
+ }
+ }
+ }
+
+ const std::string gridName = grid->metadataItem("grid_name");
+ const std::string parentName = grid->metadataItem("parent_grid_name");
+
+ auto hgrid = internal::make_unique<GTiffHGrid>(
+ std::move(grid), idxLatShift, idxLonShift, convFactorToRadian,
+ positiveEast);
+
+ insertIntoHierarchy(ctx, std::move(hgrid), gridName, parentName,
+ set->m_grids, mapGrids);
+ }
+ return set;
+}
+#endif // TIFF_ENABLED
+
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<HorizontalShiftGridSet>
+HorizontalShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) {
+ if (filename == "null") {
+ auto set = std::unique_ptr<HorizontalShiftGridSet>(
+ new HorizontalShiftGridSet());
+ set->m_name = filename;
+ set->m_format = "null";
+ set->m_grids.push_back(std::unique_ptr<NullHorizontalShiftGrid>(
+ new NullHorizontalShiftGrid()));
+ return set;
+ }
+
+ auto fp = FileManager::open_resource_file(ctx, filename.c_str());
+ if (!fp) {
+ return nullptr;
+ }
+ const auto actualName(fp->name());
+
+ char header[160];
+ /* -------------------------------------------------------------------- */
+ /* Load a header, to determine the file type. */
+ /* -------------------------------------------------------------------- */
+ size_t header_size = fp->read(header, sizeof(header));
+ if (header_size != sizeof(header)) {
+ /* some files may be smaller that sizeof(header), eg 160, so */
+ ctx->last_errno = 0; /* don't treat as a persistent error */
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "pj_gridinfo_init: short header read of %d bytes",
+ (int)header_size);
+ }
+ fp->seek(0);
+
+ /* -------------------------------------------------------------------- */
+ /* Determine file type. */
+ /* -------------------------------------------------------------------- */
+ if (header_size >= 144 + 16 && strncmp(header + 0, "HEADER", 6) == 0 &&
+ strncmp(header + 96, "W GRID", 6) == 0 &&
+ strncmp(header + 144, "TO NAD83 ", 16) == 0) {
+ auto grid = NTv1Grid::open(ctx, std::move(fp), actualName);
+ if (!grid) {
+ return nullptr;
+ }
+ auto set = std::unique_ptr<HorizontalShiftGridSet>(
+ new HorizontalShiftGridSet());
+ set->m_name = actualName;
+ set->m_format = "ntv1";
+ set->m_grids.push_back(std::unique_ptr<HorizontalShiftGrid>(grid));
+ return set;
+ } else if (header_size >= 9 && strncmp(header + 0, "CTABLE V2", 9) == 0) {
+ auto grid = CTable2Grid::open(ctx, std::move(fp), actualName);
+ if (!grid) {
+ return nullptr;
+ }
+ auto set = std::unique_ptr<HorizontalShiftGridSet>(
+ new HorizontalShiftGridSet());
+ set->m_name = actualName;
+ set->m_format = "ctable2";
+ set->m_grids.push_back(std::unique_ptr<HorizontalShiftGrid>(grid));
+ return set;
+ } else if (header_size >= 48 + 7 &&
+ strncmp(header + 0, "NUM_OREC", 8) == 0 &&
+ strncmp(header + 48, "GS_TYPE", 7) == 0) {
+ return NTv2GridSet::open(ctx, std::move(fp), actualName);
+ } else if (IsTIFF(header_size,
+ reinterpret_cast<const unsigned char *>(header))) {
+#ifdef TIFF_ENABLED
+ auto set = GTiffHGridShiftSet::open(ctx, std::move(fp), actualName);
+ if (!set)
+ pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ return set;
+#else
+ pj_log(ctx, PJ_LOG_ERROR,
+ "TIFF grid, but TIFF support disabled in this build");
+ return nullptr;
+#endif
+ }
+
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Unrecognized horizontal grid format");
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+bool HorizontalShiftGridSet::reopen(PJ_CONTEXT *ctx) {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Grid %s has changed. Re-loading it",
+ m_name.c_str());
+ auto newGS = open(ctx, m_name);
+ m_grids.clear();
+ if (newGS) {
+ m_grids = std::move(newGS->m_grids);
+ }
+ return !m_grids.empty();
+}
+
+// ---------------------------------------------------------------------------
+
+#define REL_TOLERANCE_HGRIDSHIFT 1e-5
+
+const HorizontalShiftGrid *HorizontalShiftGrid::gridAt(double lon,
+ double lat) const {
+ for (const auto &child : m_children) {
+ const auto &extentChild = child->extentAndRes();
+ const double epsilon = (extentChild.resLon + extentChild.resLat) *
+ REL_TOLERANCE_HGRIDSHIFT;
+ if ((extentChild.fullWorldLongitude() ||
+ (lon + epsilon >= extentChild.westLon &&
+ lon - epsilon <= extentChild.eastLon)) &&
+ lat + epsilon >= extentChild.southLat &&
+ lat - epsilon <= extentChild.northLat) {
+ return child->gridAt(lon, lat);
+ }
+ }
+ return this;
+}
+// ---------------------------------------------------------------------------
+
+const HorizontalShiftGrid *HorizontalShiftGridSet::gridAt(double lon,
+ double lat) const {
+ for (const auto &grid : m_grids) {
+ if (dynamic_cast<NullHorizontalShiftGrid *>(grid.get())) {
+ return grid.get();
+ }
+ const auto &extent = grid->extentAndRes();
+ const double epsilon =
+ (extent.resLon + extent.resLat) * REL_TOLERANCE_HGRIDSHIFT;
+ if ((extent.fullWorldLongitude() ||
+ (lon + epsilon >= extent.westLon &&
+ lon - epsilon <= extent.eastLon)) &&
+ lat + epsilon >= extent.southLat &&
+ lat - epsilon <= extent.northLat) {
+ return grid->gridAt(lon, lat);
+ }
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+void HorizontalShiftGridSet::reassign_context(PJ_CONTEXT *ctx) {
+ for (const auto &grid : m_grids) {
+ grid->reassign_context(ctx);
+ }
+}
+
+#ifdef TIFF_ENABLED
+// ---------------------------------------------------------------------------
+
+class GTiffGenericGridShiftSet : public GenericShiftGridSet {
+
+ std::unique_ptr<GTiffDataset> m_GTiffDataset;
+
+ GTiffGenericGridShiftSet(PJ_CONTEXT *ctx, std::unique_ptr<File> &&fp)
+ : m_GTiffDataset(new GTiffDataset(ctx, std::move(fp))) {}
+
+ public:
+ ~GTiffGenericGridShiftSet() override;
+
+ static std::unique_ptr<GTiffGenericGridShiftSet>
+ open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
+ const std::string &filename);
+
+ void reassign_context(PJ_CONTEXT *ctx) override {
+ GenericShiftGridSet::reassign_context(ctx);
+ if (m_GTiffDataset) {
+ m_GTiffDataset->reassign_context(ctx);
+ }
+ }
+
+ bool reopen(PJ_CONTEXT *ctx) override {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Grid %s has changed. Re-loading it",
+ m_name.c_str());
+ m_grids.clear();
+ m_GTiffDataset.reset();
+ auto fp = FileManager::open_resource_file(ctx, m_name.c_str());
+ if (!fp) {
+ return false;
+ }
+ auto newGS = open(ctx, std::move(fp), m_name);
+ if (newGS) {
+ m_grids = std::move(newGS->m_grids);
+ m_GTiffDataset = std::move(newGS->m_GTiffDataset);
+ }
+ return !m_grids.empty();
+ }
+};
+
+// ---------------------------------------------------------------------------
+
+class GTiffGenericGrid : public GenericShiftGrid {
+ friend void insertIntoHierarchy<GTiffGenericGrid, GenericShiftGrid>(
+ PJ_CONTEXT *ctx, std::unique_ptr<GTiffGenericGrid> &&grid,
+ const std::string &gridName, const std::string &parentName,
+ std::vector<std::unique_ptr<GenericShiftGrid>> &topGrids,
+ std::map<std::string, GTiffGenericGrid *> &mapGrids);
+
+ std::unique_ptr<GTiffGrid> m_grid;
+
+ public:
+ GTiffGenericGrid(std::unique_ptr<GTiffGrid> &&grid);
+
+ ~GTiffGenericGrid() override;
+
+ bool valueAt(int x, int y, int sample, float &out) const override;
+
+ int samplesPerPixel() const override { return m_grid->samplesPerPixel(); }
+
+ std::string unit(int sample) const override {
+ return m_grid->metadataItem("UNITTYPE", sample);
+ }
+
+ std::string description(int sample) const override {
+ return m_grid->metadataItem("DESCRIPTION", sample);
+ }
+
+ std::string metadataItem(const std::string &key,
+ int sample = -1) const override {
+ return m_grid->metadataItem(key, sample);
+ }
+
+ void insertGrid(PJ_CONTEXT *ctx,
+ std::unique_ptr<GTiffGenericGrid> &&subgrid);
+
+ void reassign_context(PJ_CONTEXT *ctx) override {
+ m_grid->reassign_context(ctx);
+ }
+
+ bool hasChanged() const override { return m_grid->hasChanged(); }
+};
+
+// ---------------------------------------------------------------------------
+
+GTiffGenericGridShiftSet::~GTiffGenericGridShiftSet() = default;
+
+// ---------------------------------------------------------------------------
+
+GTiffGenericGrid::GTiffGenericGrid(std::unique_ptr<GTiffGrid> &&grid)
+ : GenericShiftGrid(grid->name(), grid->width(), grid->height(),
+ grid->extentAndRes()),
+ m_grid(std::move(grid)) {}
+
+// ---------------------------------------------------------------------------
+
+GTiffGenericGrid::~GTiffGenericGrid() = default;
+
+// ---------------------------------------------------------------------------
+
+bool GTiffGenericGrid::valueAt(int x, int y, int sample, float &out) const {
+ if (sample < 0 ||
+ static_cast<unsigned>(sample) >= m_grid->samplesPerPixel())
+ return false;
+ return m_grid->valueAt(static_cast<uint16>(sample), x, y, out);
+}
+
+// ---------------------------------------------------------------------------
+
+void GTiffGenericGrid::insertGrid(PJ_CONTEXT *ctx,
+ std::unique_ptr<GTiffGenericGrid> &&subgrid) {
+ bool gridInserted = false;
+ const auto &extent = subgrid->extentAndRes();
+ for (const auto &candidateParent : m_children) {
+ const auto &candidateParentExtent = candidateParent->extentAndRes();
+ if (candidateParentExtent.contains(extent)) {
+ static_cast<GTiffGenericGrid *>(candidateParent.get())
+ ->insertGrid(ctx, std::move(subgrid));
+ gridInserted = true;
+ break;
+ } else if (candidateParentExtent.intersects(extent)) {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "Partially intersecting grids found!");
+ }
+ }
+ if (!gridInserted) {
+ m_children.emplace_back(std::move(subgrid));
+ }
+}
+#endif // TIFF_ENABLED
+
+// ---------------------------------------------------------------------------
+
+class NullGenericShiftGrid : public GenericShiftGrid {
+
+ public:
+ NullGenericShiftGrid() : GenericShiftGrid("null", 3, 3, globalExtent()) {}
+
+ bool isNullGrid() const override { return true; }
+ bool valueAt(int, int, int, float &out) const override;
+
+ int samplesPerPixel() const override { return 0; }
+
+ std::string unit(int) const override { return std::string(); }
+
+ std::string description(int) const override { return std::string(); }
+
+ std::string metadataItem(const std::string &, int) const override {
+ return std::string();
+ }
+
+ void reassign_context(PJ_CONTEXT *) override {}
+
+ bool hasChanged() const override { return false; }
+};
+
+// ---------------------------------------------------------------------------
+
+bool NullGenericShiftGrid::valueAt(int, int, int, float &out) const {
+ out = 0.0f;
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+#ifdef TIFF_ENABLED
+
+std::unique_ptr<GTiffGenericGridShiftSet>
+GTiffGenericGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
+ const std::string &filename) {
+ auto set = std::unique_ptr<GTiffGenericGridShiftSet>(
+ new GTiffGenericGridShiftSet(ctx, std::move(fp)));
+ set->m_name = filename;
+ set->m_format = "gtiff";
+ if (!set->m_GTiffDataset->openTIFF(filename)) {
+ return nullptr;
+ }
+
+ std::map<std::string, GTiffGenericGrid *> mapGrids;
+ for (int ifd = 0;; ++ifd) {
+ auto grid = set->m_GTiffDataset->nextGrid();
+ if (!grid) {
+ if (ifd == 0) {
+ return nullptr;
+ }
+ break;
+ }
+
+ const auto subfileType = grid->subfileType();
+ if (subfileType != 0 && subfileType != FILETYPE_PAGE) {
+ if (ifd == 0) {
+ pj_log(ctx, PJ_LOG_ERROR, "Invalid subfileType");
+ return nullptr;
+ } else {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "Ignoring IFD %d as it has a unsupported subfileType",
+ ifd);
+ continue;
+ }
+ }
+
+ const std::string gridName = grid->metadataItem("grid_name");
+ const std::string parentName = grid->metadataItem("parent_grid_name");
+
+ auto hgrid = internal::make_unique<GTiffGenericGrid>(std::move(grid));
+
+ insertIntoHierarchy(ctx, std::move(hgrid), gridName, parentName,
+ set->m_grids, mapGrids);
+ }
+ return set;
+}
+#endif // TIFF_ENABLED
+
+// ---------------------------------------------------------------------------
+
+GenericShiftGrid::GenericShiftGrid(const std::string &nameIn, int widthIn,
+ int heightIn, const ExtentAndRes &extentIn)
+ : Grid(nameIn, widthIn, heightIn, extentIn) {}
+
+// ---------------------------------------------------------------------------
+
+GenericShiftGrid::~GenericShiftGrid() = default;
+
+// ---------------------------------------------------------------------------
+
+GenericShiftGridSet::GenericShiftGridSet() = default;
+
+// ---------------------------------------------------------------------------
+
+GenericShiftGridSet::~GenericShiftGridSet() = default;
+
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<GenericShiftGridSet>
+GenericShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) {
+ if (filename == "null") {
+ auto set =
+ std::unique_ptr<GenericShiftGridSet>(new GenericShiftGridSet());
+ set->m_name = filename;
+ set->m_format = "null";
+ set->m_grids.push_back(
+ std::unique_ptr<NullGenericShiftGrid>(new NullGenericShiftGrid()));
+ return set;
+ }
+
+ auto fp = FileManager::open_resource_file(ctx, filename.c_str());
+ if (!fp) {
+ return nullptr;
+ }
+ const auto actualName(fp->name());
+
+ /* -------------------------------------------------------------------- */
+ /* Load a header, to determine the file type. */
+ /* -------------------------------------------------------------------- */
+ unsigned char header[4];
+ size_t header_size = fp->read(header, sizeof(header));
+ if (header_size != sizeof(header)) {
+ return nullptr;
+ }
+ fp->seek(0);
+
+ if (IsTIFF(header_size, header)) {
+#ifdef TIFF_ENABLED
+ auto set =
+ GTiffGenericGridShiftSet::open(ctx, std::move(fp), actualName);
+ if (!set)
+ pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ return set;
+#else
+ pj_log(ctx, PJ_LOG_ERROR,
+ "TIFF grid, but TIFF support disabled in this build");
+ return nullptr;
+#endif
+ }
+
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Unrecognized generic grid format");
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+bool GenericShiftGridSet::reopen(PJ_CONTEXT *ctx) {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Grid %s has changed. Re-loading it",
+ m_name.c_str());
+ auto newGS = open(ctx, m_name);
+ m_grids.clear();
+ if (newGS) {
+ m_grids = std::move(newGS->m_grids);
+ }
+ return !m_grids.empty();
+}
+
+// ---------------------------------------------------------------------------
+
+const GenericShiftGrid *GenericShiftGrid::gridAt(double lon, double lat) const {
+ for (const auto &child : m_children) {
+ const auto &extentChild = child->extentAndRes();
+ if ((extentChild.fullWorldLongitude() ||
+ (lon >= extentChild.westLon && lon <= extentChild.eastLon)) &&
+ lat >= extentChild.southLat && lat <= extentChild.northLat) {
+ return child->gridAt(lon, lat);
+ }
+ }
+ return this;
+}
+
+// ---------------------------------------------------------------------------
+
+const GenericShiftGrid *GenericShiftGridSet::gridAt(double lon,
+ double lat) const {
+ for (const auto &grid : m_grids) {
+ if (dynamic_cast<NullGenericShiftGrid *>(grid.get())) {
+ return grid.get();
+ }
+ const auto &extent = grid->extentAndRes();
+ if ((extent.fullWorldLongitude() ||
+ (lon >= extent.westLon && lon <= extent.eastLon)) &&
+ lat >= extent.southLat && lat <= extent.northLat) {
+ return grid->gridAt(lon, lat);
+ }
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+void GenericShiftGridSet::reassign_context(PJ_CONTEXT *ctx) {
+ for (const auto &grid : m_grids) {
+ grid->reassign_context(ctx);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+ListOfGenericGrids pj_generic_grid_init(PJ *P, const char *gridkey) {
+ std::string key("s");
+ key += gridkey;
+ const char *gridnames = pj_param(P->ctx, P->params, key.c_str()).s;
+ if (gridnames == nullptr)
+ return {};
+
+ auto listOfGridNames = internal::split(std::string(gridnames), ',');
+ ListOfGenericGrids grids;
+ for (const auto &gridnameStr : listOfGridNames) {
+ const char *gridname = gridnameStr.c_str();
+ bool canFail = false;
+ if (gridname[0] == '@') {
+ canFail = true;
+ gridname++;
+ }
+ auto gridSet = GenericShiftGridSet::open(P->ctx, gridname);
+ if (!gridSet) {
+ if (!canFail) {
+ if (proj_context_errno(P->ctx) != PJD_ERR_NETWORK_ERROR) {
+ pj_ctx_set_errno(P->ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ }
+ return {};
+ }
+ pj_ctx_set_errno(P->ctx, 0); // don't treat as a persistent error
+ } else {
+ grids.emplace_back(std::move(gridSet));
+ }
+ }
+
+ return grids;
+}
+
+// ---------------------------------------------------------------------------
+
+static const HorizontalShiftGrid *
+findGrid(const ListOfHGrids &grids, const PJ_LP &input,
+ HorizontalShiftGridSet *&gridSetOut) {
+ for (const auto &gridset : grids) {
+ auto grid = gridset->gridAt(input.lam, input.phi);
+ if (grid) {
+ gridSetOut = gridset.get();
+ return grid;
+ }
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+static ListOfHGrids getListOfGridSets(PJ_CONTEXT *ctx, const char *grids) {
+ ListOfHGrids list;
+ auto listOfGrids = internal::split(std::string(grids), ',');
+ for (const auto &grid : listOfGrids) {
+ const char *gridname = grid.c_str();
+ bool canFail = false;
+ if (gridname[0] == '@') {
+ canFail = true;
+ gridname++;
+ }
+ auto gridSet = HorizontalShiftGridSet::open(ctx, gridname);
+ if (!gridSet) {
+ if (!canFail) {
+ if (proj_context_errno(ctx) != PJD_ERR_NETWORK_ERROR) {
+ pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ }
+ return {};
+ }
+ pj_ctx_set_errno(ctx, 0); // don't treat as a persistent error
+ } else {
+ list.emplace_back(std::move(gridSet));
+ }
+ }
+ return list;
+}
+
+/**********************************************/
+ListOfHGrids pj_hgrid_init(PJ *P, const char *gridkey) {
+ /**********************************************
+
+ Initizalize and populate list of horizontal
+ grids.
+
+ Takes a PJ-object and the plus-parameter
+ name that is used in the proj-string to
+ specify the grids to load, e.g. "+grids".
+ The + should be left out here.
+
+ Returns the number of loaded grids.
+
+ ***********************************************/
+
+ std::string key("s");
+ key += gridkey;
+ const char *grids = pj_param(P->ctx, P->params, key.c_str()).s;
+ if (grids == nullptr)
+ return {};
+
+ return getListOfGridSets(P->ctx, grids);
+}
+
+// ---------------------------------------------------------------------------
+
+typedef struct { pj_int32 lam, phi; } ILP;
+
+// Apply bilinear interpolation for horizontal shift grids
+static PJ_LP pj_hgrid_interpolate(PJ_LP t, const HorizontalShiftGrid *grid,
+ bool compensateNTConvention) {
+ PJ_LP val, frct;
+ ILP indx;
+ int in;
+
+ const auto &extent = grid->extentAndRes();
+ t.lam /= extent.resLon;
+ indx.lam = std::isnan(t.lam) ? 0 : (pj_int32)lround(floor(t.lam));
+ t.phi /= extent.resLat;
+ indx.phi = std::isnan(t.phi) ? 0 : (pj_int32)lround(floor(t.phi));
+
+ frct.lam = t.lam - indx.lam;
+ frct.phi = t.phi - indx.phi;
+ val.lam = val.phi = HUGE_VAL;
+ if (indx.lam < 0) {
+ if (indx.lam == -1 && frct.lam > 1 - 10 * REL_TOLERANCE_HGRIDSHIFT) {
+ ++indx.lam;
+ frct.lam = 0.;
+ } else
+ return val;
+ } else if ((in = indx.lam + 1) >= grid->width()) {
+ if (in == grid->width() && frct.lam < 10 * REL_TOLERANCE_HGRIDSHIFT) {
+ --indx.lam;
+ frct.lam = 1.;
+ } else
+ return val;
+ }
+ if (indx.phi < 0) {
+ if (indx.phi == -1 && frct.phi > 1 - 10 * REL_TOLERANCE_HGRIDSHIFT) {
+ ++indx.phi;
+ frct.phi = 0.;
+ } else
+ return val;
+ } else if ((in = indx.phi + 1) >= grid->height()) {
+ if (in == grid->height() && frct.phi < 10 * REL_TOLERANCE_HGRIDSHIFT) {
+ --indx.phi;
+ frct.phi = 1.;
+ } else
+ return val;
+ }
+
+ float f00Lon = 0, f00Lat = 0;
+ float f10Lon = 0, f10Lat = 0;
+ float f01Lon = 0, f01Lat = 0;
+ float f11Lon = 0, f11Lat = 0;
+ if (!grid->valueAt(indx.lam, indx.phi, compensateNTConvention, f00Lon,
+ f00Lat) ||
+ !grid->valueAt(indx.lam + 1, indx.phi, compensateNTConvention, f10Lon,
+ f10Lat) ||
+ !grid->valueAt(indx.lam, indx.phi + 1, compensateNTConvention, f01Lon,
+ f01Lat) ||
+ !grid->valueAt(indx.lam + 1, indx.phi + 1, compensateNTConvention,
+ f11Lon, f11Lat)) {
+ return val;
+ }
+
+ double m10 = frct.lam;
+ double m11 = m10;
+ double m01 = 1. - frct.lam;
+ double m00 = m01;
+ m11 *= frct.phi;
+ m01 *= frct.phi;
+ frct.phi = 1. - frct.phi;
+ m00 *= frct.phi;
+ m10 *= frct.phi;
+ val.lam = m00 * f00Lon + m10 * f10Lon + m01 * f01Lon + m11 * f11Lon;
+ val.phi = m00 * f00Lat + m10 * f10Lat + m01 * f01Lat + m11 * f11Lat;
+ return val;
+}
+
+// ---------------------------------------------------------------------------
+
+#define MAX_ITERATIONS 10
+#define TOL 1e-12
+
+static PJ_LP pj_hgrid_apply_internal(projCtx ctx, PJ_LP in,
+ PJ_DIRECTION direction,
+ const HorizontalShiftGrid *grid,
+ HorizontalShiftGridSet *gridset,
+ const ListOfHGrids &grids,
+ bool &shouldRetry) {
+ PJ_LP t, tb, del, dif;
+ int i = MAX_ITERATIONS;
+ const double toltol = TOL * TOL;
+
+ shouldRetry = false;
+ if (in.lam == HUGE_VAL)
+ return in;
+
+ /* normalize input to ll origin */
+ tb = in;
+ const auto *extent = &(grid->extentAndRes());
+ tb.lam -= extent->westLon;
+ tb.phi -= extent->southLat;
+
+ t = pj_hgrid_interpolate(tb, grid, true);
+ if (grid->hasChanged()) {
+ shouldRetry = gridset->reopen(ctx);
+ return t;
+ }
+ if (t.lam == HUGE_VAL)
+ return t;
+
+ if (direction == PJ_FWD) {
+ in.lam += t.lam;
+ in.phi += t.phi;
+ return in;
+ }
+
+ t.lam = tb.lam - t.lam;
+ t.phi = tb.phi - t.phi;
+
+ do {
+ del = pj_hgrid_interpolate(t, grid, true);
+ if (grid->hasChanged()) {
+ shouldRetry = gridset->reopen(ctx);
+ return t;
+ }
+
+ /* We can possibly go outside of the initial guessed grid, so try */
+ /* to fetch a new grid into which iterate... */
+ if (del.lam == HUGE_VAL) {
+ PJ_LP lp;
+ lp.lam = t.lam + extent->westLon;
+ lp.phi = t.phi + extent->southLat;
+ auto newGrid = findGrid(grids, lp, gridset);
+ if (newGrid == nullptr || newGrid == grid || newGrid->isNullGrid())
+ break;
+ pj_log(ctx, PJ_LOG_DEBUG_MINOR, "Switching from grid %s to grid %s",
+ grid->name().c_str(), newGrid->name().c_str());
+ grid = newGrid;
+ extent = &(grid->extentAndRes());
+ t.lam = lp.lam - extent->westLon;
+ t.phi = lp.phi - extent->southLat;
+ tb = in;
+ tb.lam -= extent->westLon;
+ tb.phi -= extent->southLat;
+ dif.lam = std::numeric_limits<double>::max();
+ dif.phi = std::numeric_limits<double>::max();
+ continue;
+ }
+
+ dif.lam = t.lam + del.lam - tb.lam;
+ dif.phi = t.phi + del.phi - tb.phi;
+ t.lam -= dif.lam;
+ t.phi -= dif.phi;
+
+ } while (--i && (dif.lam * dif.lam + dif.phi * dif.phi >
+ toltol)); /* prob. slightly faster than hypot() */
+
+ if (i == 0) {
+ /* If we had access to a context, this should go through pj_log, and we
+ * should set ctx->errno */
+ if (getenv("PROJ_DEBUG"))
+ fprintf(stderr,
+ "Inverse grid shift iterator failed to converge.\n");
+ t.lam = t.phi = HUGE_VAL;
+ return t;
+ }
+
+ /* and again: pj_log and ctx->errno */
+ if (del.lam == HUGE_VAL && getenv("PROJ_DEBUG"))
+ fprintf(stderr, "Inverse grid shift iteration failed, presumably at "
+ "grid edge.\nUsing first approximation.\n");
+
+ in.lam = adjlon(t.lam + extent->westLon);
+ in.phi = t.phi + extent->southLat;
+ return in;
+}
+
+// ---------------------------------------------------------------------------
+
+PJ_LP pj_hgrid_apply(PJ_CONTEXT *ctx, const ListOfHGrids &grids, PJ_LP lp,
+ PJ_DIRECTION direction) {
+ PJ_LP out;
+
+ out.lam = HUGE_VAL;
+ out.phi = HUGE_VAL;
+
+ while (true) {
+ HorizontalShiftGridSet *gridset = nullptr;
+ const auto grid = findGrid(grids, lp, gridset);
+ if (!grid) {
+ pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ return out;
+ }
+ if (grid->isNullGrid()) {
+ return lp;
+ }
+
+ bool shouldRetry = false;
+ out = pj_hgrid_apply_internal(ctx, lp, direction, grid, gridset, grids,
+ shouldRetry);
+ if (!shouldRetry) {
+ break;
+ }
+ }
+
+ if (out.lam == HUGE_VAL || out.phi == HUGE_VAL)
+ pj_ctx_set_errno(ctx, PJD_ERR_GRID_AREA);
+
+ return out;
+}
+
+/********************************************/
+/* proj_hgrid_value() */
+/* */
+/* Return coordinate offset in grid */
+/********************************************/
+PJ_LP pj_hgrid_value(PJ *P, const ListOfHGrids &grids, PJ_LP lp) {
+ PJ_LP out = proj_coord_error().lp;
+
+ HorizontalShiftGridSet *gridset = nullptr;
+ const auto grid = findGrid(grids, lp, gridset);
+ if (!grid) {
+ pj_ctx_set_errno(P->ctx, PJD_ERR_GRID_AREA);
+ return out;
+ }
+
+ /* normalize input to ll origin */
+ const auto &extent = grid->extentAndRes();
+ lp.lam -= extent.westLon;
+ lp.phi -= extent.southLat;
+
+ lp.lam = adjlon(lp.lam - M_PI) + M_PI;
+
+ out = pj_hgrid_interpolate(lp, grid, false);
+ if (grid->hasChanged()) {
+ if (gridset->reopen(P->ctx)) {
+ return pj_hgrid_value(P, grids, lp);
+ }
+ out.lam = HUGE_VAL;
+ out.phi = HUGE_VAL;
+ }
+
+ if (out.lam == HUGE_VAL || out.phi == HUGE_VAL) {
+ pj_ctx_set_errno(P->ctx, PJD_ERR_GRID_AREA);
+ }
+
+ return out;
+}
+
+// ---------------------------------------------------------------------------
+
+static double read_vgrid_value(PJ_CONTEXT *ctx, const ListOfVGrids &grids,
+ const PJ_LP &input, const double vmultiplier) {
+
+ /* do not deal with NaN coordinates */
+ /* cppcheck-suppress duplicateExpression */
+ if (std::isnan(input.phi) || std::isnan(input.lam)) {
+ return HUGE_VAL;
+ }
+
+ VerticalShiftGridSet *curGridset = nullptr;
+ const VerticalShiftGrid *grid = nullptr;
+ for (const auto &gridset : grids) {
+ grid = gridset->gridAt(input.lam, input.phi);
+ if (grid) {
+ curGridset = gridset.get();
+ break;
+ }
+ }
+ if (!grid) {
+ return HUGE_VAL;
+ }
+
+ const auto &extent = grid->extentAndRes();
+
+ /* Interpolation a location within the grid */
+ double grid_x = (input.lam - extent.westLon) / extent.resLon;
+ if (extent.fullWorldLongitude()) {
+ // The first fmod goes to ]-lim, lim[ range
+ // So we add lim again to be in ]0, 2*lim[ and fmod again
+ grid_x =
+ fmod(fmod(grid_x + grid->width(), grid->width()) + grid->width(),
+ grid->width());
+ }
+ double grid_y = (input.phi - extent.southLat) / extent.resLat;
+ int grid_ix = static_cast<int>(lround(floor(grid_x)));
+ assert(grid_ix >= 0 && grid_ix < grid->width());
+ int grid_iy = static_cast<int>(lround(floor(grid_y)));
+ assert(grid_iy >= 0 && grid_iy < grid->height());
+ grid_x -= grid_ix;
+ grid_y -= grid_iy;
+
+ int grid_ix2 = grid_ix + 1;
+ if (grid_ix2 >= grid->width()) {
+ if (extent.fullWorldLongitude()) {
+ grid_ix2 = 0;
+ } else {
+ grid_ix2 = grid->width() - 1;
+ }
+ }
+ int grid_iy2 = grid_iy + 1;
+ if (grid_iy2 >= grid->height())
+ grid_iy2 = grid->height() - 1;
+
+ float value_a = 0;
+ float value_b = 0;
+ float value_c = 0;
+ float value_d = 0;
+ bool error = (!grid->valueAt(grid_ix, grid_iy, value_a) ||
+ !grid->valueAt(grid_ix2, grid_iy, value_b) ||
+ !grid->valueAt(grid_ix, grid_iy2, value_c) ||
+ !grid->valueAt(grid_ix2, grid_iy2, value_d));
+ if (grid->hasChanged()) {
+ if (curGridset->reopen(ctx)) {
+ return read_vgrid_value(ctx, grids, input, vmultiplier);
+ }
+ error = true;
+ }
+
+ if (error) {
+ return HUGE_VAL;
+ }
+
+ double total_weight = 0.0;
+ int n_weights = 0;
+ double value = 0.0f;
+
+ if (!grid->isNodata(value_a, vmultiplier)) {
+ double weight = (1.0 - grid_x) * (1.0 - grid_y);
+ value += value_a * weight;
+ total_weight += weight;
+ n_weights++;
+ }
+ if (!grid->isNodata(value_b, vmultiplier)) {
+ double weight = (grid_x) * (1.0 - grid_y);
+ value += value_b * weight;
+ total_weight += weight;
+ n_weights++;
+ }
+ if (!grid->isNodata(value_c, vmultiplier)) {
+ double weight = (1.0 - grid_x) * (grid_y);
+ value += value_c * weight;
+ total_weight += weight;
+ n_weights++;
+ }
+ if (!grid->isNodata(value_d, vmultiplier)) {
+ double weight = (grid_x) * (grid_y);
+ value += value_d * weight;
+ total_weight += weight;
+ n_weights++;
+ }
+ if (n_weights == 0)
+ value = HUGE_VAL;
+ else if (n_weights != 4)
+ value /= total_weight;
+
+ return value * vmultiplier;
+}
+
+/**********************************************/
+ListOfVGrids pj_vgrid_init(PJ *P, const char *gridkey) {
+ /**********************************************
+
+ Initizalize and populate gridlist.
+
+ Takes a PJ-object and the plus-parameter
+ name that is used in the proj-string to
+ specify the grids to load, e.g. "+grids".
+ The + should be left out here.
+
+ Returns the number of loaded grids.
+
+ ***********************************************/
+
+ std::string key("s");
+ key += gridkey;
+ const char *gridnames = pj_param(P->ctx, P->params, key.c_str()).s;
+ if (gridnames == nullptr)
+ return {};
+
+ auto listOfGridNames = internal::split(std::string(gridnames), ',');
+ ListOfVGrids grids;
+ for (const auto &gridnameStr : listOfGridNames) {
+ const char *gridname = gridnameStr.c_str();
+ bool canFail = false;
+ if (gridname[0] == '@') {
+ canFail = true;
+ gridname++;
+ }
+ auto gridSet = VerticalShiftGridSet::open(P->ctx, gridname);
+ if (!gridSet) {
+ if (!canFail) {
+ if (proj_context_errno(P->ctx) != PJD_ERR_NETWORK_ERROR) {
+ pj_ctx_set_errno(P->ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ }
+ return {};
+ }
+ pj_ctx_set_errno(P->ctx, 0); // don't treat as a persistent error
+ } else {
+ grids.emplace_back(std::move(gridSet));
+ }
+ }
+
+ return grids;
+}
+
+/***********************************************/
+double pj_vgrid_value(PJ *P, const ListOfVGrids &grids, PJ_LP lp,
+ double vmultiplier) {
+ /***********************************************
+
+ Read grid value at position lp in grids loaded
+ with proj_grid_init.
+
+ Returns the grid value of the given coordinate.
+
+ ************************************************/
+
+ double value;
+
+ value = read_vgrid_value(P->ctx, grids, lp, vmultiplier);
+ proj_log_trace(P, "proj_vgrid_value: (%f, %f) = %f", lp.lam * RAD_TO_DEG,
+ lp.phi * RAD_TO_DEG, value);
+
+ return value;
+}
+
+// ---------------------------------------------------------------------------
+
+const GenericShiftGrid *pj_find_generic_grid(const ListOfGenericGrids &grids,
+ const PJ_LP &input,
+ GenericShiftGridSet *&gridSetOut) {
+ for (const auto &gridset : grids) {
+ auto grid = gridset->gridAt(input.lam, input.phi);
+ if (grid) {
+ gridSetOut = gridset.get();
+ return grid;
+ }
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+// Used by +proj=deformation and +proj=xyzgridshift to do bilinear interpolation
+// on 3 sample values per node.
+bool pj_bilinear_interpolation_three_samples(const GenericShiftGrid *grid,
+ const PJ_LP &lp, int idx1,
+ int idx2, int idx3, double &v1,
+ double &v2, double &v3,
+ bool &must_retry) {
+ must_retry = false;
+
+ const auto &extent = grid->extentAndRes();
+ double grid_x = (lp.lam - extent.westLon) / extent.resLon;
+ double grid_y = (lp.phi - extent.southLat) / extent.resLat;
+ int ix = static_cast<int>(grid_x);
+ int iy = static_cast<int>(grid_y);
+ int ix2 = std::min(ix + 1, grid->width() - 1);
+ int iy2 = std::min(iy + 1, grid->height() - 1);
+
+ float dx1 = 0.0f, dy1 = 0.0f, dz1 = 0.0f;
+ float dx2 = 0.0f, dy2 = 0.0f, dz2 = 0.0f;
+ float dx3 = 0.0f, dy3 = 0.0f, dz3 = 0.0f;
+ float dx4 = 0.0f, dy4 = 0.0f, dz4 = 0.0f;
+ bool error = (!grid->valueAt(ix, iy, idx1, dx1) ||
+ !grid->valueAt(ix, iy, idx2, dy1) ||
+ !grid->valueAt(ix, iy, idx3, dz1) ||
+ !grid->valueAt(ix2, iy, idx1, dx2) ||
+ !grid->valueAt(ix2, iy, idx2, dy2) ||
+ !grid->valueAt(ix2, iy, idx3, dz2) ||
+ !grid->valueAt(ix, iy2, idx1, dx3) ||
+ !grid->valueAt(ix, iy2, idx2, dy3) ||
+ !grid->valueAt(ix, iy2, idx3, dz3) ||
+ !grid->valueAt(ix2, iy2, idx1, dx4) ||
+ !grid->valueAt(ix2, iy2, idx2, dy4) ||
+ !grid->valueAt(ix2, iy2, idx3, dz4));
+ if (grid->hasChanged()) {
+ must_retry = true;
+ return false;
+ }
+ if (error) {
+ return false;
+ }
+
+ double frct_lam = grid_x - ix;
+ double frct_phi = grid_y - iy;
+ double m10 = frct_lam;
+ double m11 = m10;
+ double m01 = 1. - frct_lam;
+ double m00 = m01;
+ m11 *= frct_phi;
+ m01 *= frct_phi;
+ frct_phi = 1. - frct_phi;
+ m00 *= frct_phi;
+ m10 *= frct_phi;
+
+ v1 = m00 * dx1 + m10 * dx2 + m01 * dx3 + m11 * dx4;
+ v2 = m00 * dy1 + m10 * dy2 + m01 * dy3 + m11 * dy4;
+ v3 = m00 * dz1 + m10 * dz2 + m01 * dz3 + m11 * dz4;
+ return true;
+}
+
+NS_PROJ_END
+
+/************************************************************************/
+/* pj_apply_gridshift() */
+/* */
+/* This is the externally callable interface - part of the */
+/* public API - though it is not used internally any more and I */
+/* doubt it is used by any other applications. But we preserve */
+/* it to honour our public api. */
+/************************************************************************/
+
+int pj_apply_gridshift(projCtx ctx, const char *nadgrids, int inverse,
+ long point_count, int point_offset, double *x, double *y,
+ double * /*z */)
+
+{
+ auto hgrids = NS_PROJ::getListOfGridSets(ctx, nadgrids);
+ if (hgrids.empty()) {
+ pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID);
+ return 1;
+ }
+
+ for (long i = 0; i < point_count; i++) {
+ PJ_LP input;
+
+ long io = i * point_offset;
+ input.phi = y[io];
+ input.lam = x[io];
+
+ auto output =
+ pj_hgrid_apply(ctx, hgrids, input, inverse ? PJ_INV : PJ_FWD);
+
+ if (output.lam != HUGE_VAL) {
+ y[io] = output.phi;
+ x[io] = output.lam;
+ } else {
+ if (ctx->debug_level >= PJ_LOG_DEBUG_MAJOR) {
+ pj_log(ctx, PJ_LOG_DEBUG_MAJOR,
+ "pj_apply_gridshift(): failed to find a grid shift "
+ "table for\n"
+ " location (%.7fdW,%.7fdN)",
+ x[io] * RAD_TO_DEG, y[io] * RAD_TO_DEG);
+ }
+ }
+ }
+
+ return 0;
+}
diff --git a/src/grids.hpp b/src/grids.hpp
new file mode 100644
index 00000000..0fd1b7b0
--- /dev/null
+++ b/src/grids.hpp
@@ -0,0 +1,264 @@
+/******************************************************************************
+ * Project: PROJ
+ * Purpose: Grid management
+ * Author: Even Rouault, <even.rouault at spatialys.com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2019, Even Rouault, <even.rouault at spatialys.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *****************************************************************************/
+
+#ifndef GRIDS_HPP_INCLUDED
+#define GRIDS_HPP_INCLUDED
+
+#include <memory>
+#include <vector>
+
+#include "proj.h"
+#include "proj/util.hpp"
+
+NS_PROJ_START
+
+struct ExtentAndRes {
+ double westLon; // in radian
+ double southLat; // in radian
+ double eastLon; // in radian
+ double northLat; // in radian
+ double resLon; // in radian
+ double resLat; // in radian
+
+ bool fullWorldLongitude() const;
+ bool contains(const ExtentAndRes &other) const;
+ bool intersects(const ExtentAndRes &other) const;
+};
+
+// ---------------------------------------------------------------------------
+
+class PROJ_GCC_DLL Grid {
+ protected:
+ std::string m_name;
+ int m_width;
+ int m_height;
+ ExtentAndRes m_extent;
+
+ Grid(const std::string &nameIn, int widthIn, int heightIn,
+ const ExtentAndRes &extentIn);
+
+ public:
+ PROJ_FOR_TEST virtual ~Grid();
+
+ PROJ_FOR_TEST int width() const { return m_width; }
+ PROJ_FOR_TEST int height() const { return m_height; }
+ PROJ_FOR_TEST const ExtentAndRes &extentAndRes() const { return m_extent; }
+ PROJ_FOR_TEST const std::string &name() const { return m_name; }
+
+ PROJ_FOR_TEST virtual bool isNullGrid() const { return false; }
+ PROJ_FOR_TEST virtual bool hasChanged() const = 0;
+};
+
+// ---------------------------------------------------------------------------
+
+class PROJ_GCC_DLL VerticalShiftGrid : public Grid {
+ protected:
+ std::vector<std::unique_ptr<VerticalShiftGrid>> m_children{};
+
+ public:
+ PROJ_FOR_TEST VerticalShiftGrid(const std::string &nameIn, int widthIn,
+ int heightIn, const ExtentAndRes &extentIn);
+ PROJ_FOR_TEST ~VerticalShiftGrid() override;
+
+ PROJ_FOR_TEST const VerticalShiftGrid *gridAt(double lon, double lat) const;
+
+ PROJ_FOR_TEST virtual bool isNodata(float /*val*/,
+ double /* multiplier */) const = 0;
+
+ // x = 0 is western-most column, y = 0 is southern-most line
+ PROJ_FOR_TEST virtual bool valueAt(int x, int y, float &out) const = 0;
+
+ PROJ_FOR_TEST virtual void reassign_context(PJ_CONTEXT *ctx) = 0;
+};
+
+// ---------------------------------------------------------------------------
+
+class PROJ_GCC_DLL VerticalShiftGridSet {
+ protected:
+ std::string m_name{};
+ std::string m_format{};
+ std::vector<std::unique_ptr<VerticalShiftGrid>> m_grids{};
+
+ VerticalShiftGridSet();
+
+ public:
+ PROJ_FOR_TEST virtual ~VerticalShiftGridSet();
+
+ PROJ_FOR_TEST static std::unique_ptr<VerticalShiftGridSet>
+ open(PJ_CONTEXT *ctx, const std::string &filename);
+
+ PROJ_FOR_TEST const std::string &name() const { return m_name; }
+ PROJ_FOR_TEST const std::string &format() const { return m_format; }
+ PROJ_FOR_TEST const std::vector<std::unique_ptr<VerticalShiftGrid>> &
+ grids() const {
+ return m_grids;
+ }
+ PROJ_FOR_TEST const VerticalShiftGrid *gridAt(double lon, double lat) const;
+
+ PROJ_FOR_TEST virtual void reassign_context(PJ_CONTEXT *ctx);
+ PROJ_FOR_TEST virtual bool reopen(PJ_CONTEXT *ctx);
+};
+
+// ---------------------------------------------------------------------------
+
+class PROJ_GCC_DLL HorizontalShiftGrid : public Grid {
+ protected:
+ std::vector<std::unique_ptr<HorizontalShiftGrid>> m_children{};
+
+ public:
+ PROJ_FOR_TEST HorizontalShiftGrid(const std::string &nameIn, int widthIn,
+ int heightIn,
+ const ExtentAndRes &extentIn);
+ PROJ_FOR_TEST ~HorizontalShiftGrid() override;
+
+ PROJ_FOR_TEST const HorizontalShiftGrid *gridAt(double lon,
+ double lat) const;
+
+ // x = 0 is western-most column, y = 0 is southern-most line
+ PROJ_FOR_TEST virtual bool valueAt(int x, int y,
+ bool compensateNTConvention,
+ float &lonShift,
+ float &latShift) const = 0;
+
+ PROJ_FOR_TEST virtual void reassign_context(PJ_CONTEXT *ctx) = 0;
+};
+
+// ---------------------------------------------------------------------------
+
+class PROJ_GCC_DLL HorizontalShiftGridSet {
+ protected:
+ std::string m_name{};
+ std::string m_format{};
+ std::vector<std::unique_ptr<HorizontalShiftGrid>> m_grids{};
+
+ HorizontalShiftGridSet();
+
+ public:
+ PROJ_FOR_TEST virtual ~HorizontalShiftGridSet();
+
+ PROJ_FOR_TEST static std::unique_ptr<HorizontalShiftGridSet>
+ open(PJ_CONTEXT *ctx, const std::string &filename);
+
+ PROJ_FOR_TEST const std::string &name() const { return m_name; }
+ PROJ_FOR_TEST const std::string &format() const { return m_format; }
+ PROJ_FOR_TEST const std::vector<std::unique_ptr<HorizontalShiftGrid>> &
+ grids() const {
+ return m_grids;
+ }
+ PROJ_FOR_TEST const HorizontalShiftGrid *gridAt(double lon,
+ double lat) const;
+
+ PROJ_FOR_TEST virtual void reassign_context(PJ_CONTEXT *ctx);
+ PROJ_FOR_TEST virtual bool reopen(PJ_CONTEXT *ctx);
+};
+
+// ---------------------------------------------------------------------------
+
+class PROJ_GCC_DLL GenericShiftGrid : public Grid {
+ protected:
+ std::vector<std::unique_ptr<GenericShiftGrid>> m_children{};
+
+ public:
+ PROJ_FOR_TEST GenericShiftGrid(const std::string &nameIn, int widthIn,
+ int heightIn, const ExtentAndRes &extentIn);
+
+ PROJ_FOR_TEST ~GenericShiftGrid() override;
+
+ PROJ_FOR_TEST const GenericShiftGrid *gridAt(double lon, double lat) const;
+
+ PROJ_FOR_TEST virtual std::string unit(int sample) const = 0;
+
+ PROJ_FOR_TEST virtual std::string description(int sample) const = 0;
+
+ PROJ_FOR_TEST virtual std::string metadataItem(const std::string &key,
+ int sample = -1) const = 0;
+
+ PROJ_FOR_TEST virtual int samplesPerPixel() const = 0;
+
+ // x = 0 is western-most column, y = 0 is southern-most line
+ PROJ_FOR_TEST virtual bool valueAt(int x, int y, int sample,
+ float &out) const = 0;
+
+ PROJ_FOR_TEST virtual void reassign_context(PJ_CONTEXT *ctx) = 0;
+};
+
+// ---------------------------------------------------------------------------
+
+class PROJ_GCC_DLL GenericShiftGridSet {
+ protected:
+ std::string m_name{};
+ std::string m_format{};
+ std::vector<std::unique_ptr<GenericShiftGrid>> m_grids{};
+
+ GenericShiftGridSet();
+
+ public:
+ PROJ_FOR_TEST virtual ~GenericShiftGridSet();
+
+ PROJ_FOR_TEST static std::unique_ptr<GenericShiftGridSet>
+ open(PJ_CONTEXT *ctx, const std::string &filename);
+
+ PROJ_FOR_TEST const std::string &name() const { return m_name; }
+ PROJ_FOR_TEST const std::string &format() const { return m_format; }
+ PROJ_FOR_TEST const std::vector<std::unique_ptr<GenericShiftGrid>> &
+ grids() const {
+ return m_grids;
+ }
+ PROJ_FOR_TEST const GenericShiftGrid *gridAt(double lon, double lat) const;
+
+ PROJ_FOR_TEST virtual void reassign_context(PJ_CONTEXT *ctx);
+ PROJ_FOR_TEST virtual bool reopen(PJ_CONTEXT *ctx);
+};
+
+// ---------------------------------------------------------------------------
+
+typedef std::vector<std::unique_ptr<HorizontalShiftGridSet>> ListOfHGrids;
+typedef std::vector<std::unique_ptr<VerticalShiftGridSet>> ListOfVGrids;
+typedef std::vector<std::unique_ptr<GenericShiftGridSet>> ListOfGenericGrids;
+
+ListOfVGrids pj_vgrid_init(PJ *P, const char *grids);
+ListOfHGrids pj_hgrid_init(PJ *P, const char *grids);
+ListOfGenericGrids pj_generic_grid_init(PJ *P, const char *grids);
+
+PJ_LP pj_hgrid_value(PJ *P, const ListOfHGrids &grids, PJ_LP lp);
+double pj_vgrid_value(PJ *P, const ListOfVGrids &, PJ_LP lp,
+ double vmultiplier);
+PJ_LP pj_hgrid_apply(PJ_CONTEXT *ctx, const ListOfHGrids &grids, PJ_LP lp,
+ PJ_DIRECTION direction);
+
+const GenericShiftGrid *pj_find_generic_grid(const ListOfGenericGrids &grids,
+ const PJ_LP &input,
+ GenericShiftGridSet *&gridSetOut);
+bool pj_bilinear_interpolation_three_samples(const GenericShiftGrid *grid,
+ const PJ_LP &lp, int idx1,
+ int idx2, int idx3, double &v1,
+ double &v2, double &v3,
+ bool &must_retry);
+
+NS_PROJ_END
+
+#endif // GRIDS_HPP_INCLUDED
diff --git a/src/init.cpp b/src/init.cpp
index 1ed46e5a..19fcf47b 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -646,12 +646,6 @@ pj_init_ctx_with_allow_init_epsg(projCtx ctx, int argc, char **argv, int allow_i
PIN->long_wrap_center = 0.0;
strcpy( PIN->axis, "enu" );
- PIN->gridlist = nullptr;
- PIN->gridlist_count = 0;
-
- PIN->vgridlist_geoid = nullptr;
- PIN->vgridlist_geoid_count = 0;
-
/* Set datum parameters. Similarly to +init parameters we want to expand */
/* +datum parameters as late as possible when dealing with pipelines. */
/* otherwise only the first occurrence of +datum will be expanded and that */
diff --git a/src/iso19111/c_api.cpp b/src/iso19111/c_api.cpp
index 9f73f8e9..4c98af3d 100644
--- a/src/iso19111/c_api.cpp
+++ b/src/iso19111/c_api.cpp
@@ -177,7 +177,11 @@ static PJ *pj_obj_create(PJ_CONTEXT *ctx, const IdentifiedObjectNNPtr &objIn) {
auto formatter = PROJStringFormatter::create(
PROJStringFormatter::Convention::PROJ_5, dbContext);
auto projString = coordop->exportToPROJString(formatter.get());
+ if (pj_context_is_network_enabled(ctx)) {
+ ctx->defer_grid_opening = true;
+ }
auto pj = pj_create_internal(ctx, projString.c_str());
+ ctx->defer_grid_opening = false;
if (pj) {
pj->iso_obj = objIn;
if (ctx->cpp_context) {
@@ -766,7 +770,7 @@ int PROJ_DLL proj_grid_get_info_from_database(
bool open_license;
bool available;
if (!db_context->lookForGridInfo(
- grid_name, ctx->cpp_context->lastGridFullName_,
+ grid_name, false, ctx->cpp_context->lastGridFullName_,
ctx->cpp_context->lastGridPackageName_,
ctx->cpp_context->lastGridUrl_, direct_download, open_license,
available)) {
@@ -6615,7 +6619,10 @@ int proj_coordoperation_is_instantiable(PJ_CONTEXT *ctx,
}
auto dbContext = getDBcontextNoException(ctx, __FUNCTION__);
try {
- auto ret = op->isPROJInstantiable(dbContext) ? 1 : 0;
+ auto ret = op->isPROJInstantiable(dbContext,
+ pj_context_is_network_enabled(ctx))
+ ? 1
+ : 0;
if (ctx->cpp_context) {
ctx->cpp_context->autoCloseDbIfNeeded();
}
@@ -6927,7 +6934,8 @@ int proj_coordoperation_get_grid_used_count(PJ_CONTEXT *ctx,
try {
if (!coordoperation->gridsNeededAsked) {
coordoperation->gridsNeededAsked = true;
- const auto gridsNeeded = co->gridsNeeded(dbContext);
+ const auto gridsNeeded =
+ co->gridsNeeded(dbContext, pj_context_is_network_enabled(ctx));
for (const auto &gridDesc : gridsNeeded) {
coordoperation->gridsNeeded.emplace_back(gridDesc);
}
@@ -7264,6 +7272,12 @@ void PROJ_DLL proj_operation_factory_context_set_grid_availability_use(
CoordinateOperationContext::GridAvailabilityUse::
IGNORE_GRID_AVAILABILITY);
break;
+
+ case PROJ_GRID_AVAILABILITY_KNOWN_AVAILABLE:
+ factory_ctx->operationContext->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ KNOWN_AVAILABLE);
+ break;
}
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
diff --git a/src/iso19111/coordinateoperation.cpp b/src/iso19111/coordinateoperation.cpp
index aaa0c413..a1650046 100644
--- a/src/iso19111/coordinateoperation.cpp
+++ b/src/iso19111/coordinateoperation.cpp
@@ -788,13 +788,15 @@ void CoordinateOperation::setAccuracies(
* available.
*/
bool CoordinateOperation::isPROJInstantiable(
- const io::DatabaseContextPtr &databaseContext) const {
+ const io::DatabaseContextPtr &databaseContext,
+ bool considerKnownGridsAsAvailable) const {
try {
exportToPROJString(io::PROJStringFormatter::create().get());
} catch (const std::exception &) {
return false;
}
- for (const auto &gridDesc : gridsNeeded(databaseContext)) {
+ for (const auto &gridDesc :
+ gridsNeeded(databaseContext, considerKnownGridsAsAvailable)) {
if (!gridDesc.available) {
return false;
}
@@ -2020,8 +2022,9 @@ bool SingleOperation::_isEquivalentTo(const util::IComparable *other,
// ---------------------------------------------------------------------------
-std::set<GridDescription> SingleOperation::gridsNeeded(
- const io::DatabaseContextPtr &databaseContext) const {
+std::set<GridDescription>
+SingleOperation::gridsNeeded(const io::DatabaseContextPtr &databaseContext,
+ bool considerKnownGridsAsAvailable) const {
std::set<GridDescription> res;
for (const auto &genOpParamvalue : parameterValues()) {
auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
@@ -2033,9 +2036,9 @@ std::set<GridDescription> SingleOperation::gridsNeeded(
desc.shortName = value->valueFile();
if (databaseContext) {
databaseContext->lookForGridInfo(
- desc.shortName, desc.fullName, desc.packageName,
- desc.url, desc.directDownload, desc.openLicense,
- desc.available);
+ desc.shortName, considerKnownGridsAsAvailable,
+ desc.fullName, desc.packageName, desc.url,
+ desc.directDownload, desc.openLicense, desc.available);
}
res.insert(desc);
}
@@ -10233,10 +10236,12 @@ bool ConcatenatedOperation::_isEquivalentTo(
// ---------------------------------------------------------------------------
std::set<GridDescription> ConcatenatedOperation::gridsNeeded(
- const io::DatabaseContextPtr &databaseContext) const {
+ const io::DatabaseContextPtr &databaseContext,
+ bool considerKnownGridsAsAvailable) const {
std::set<GridDescription> res;
for (const auto &operation : operations()) {
- const auto l_gridsNeeded = operation->gridsNeeded(databaseContext);
+ const auto l_gridsNeeded = operation->gridsNeeded(
+ databaseContext, considerKnownGridsAsAvailable);
for (const auto &gridDesc : l_gridsNeeded) {
res.insert(gridDesc);
}
@@ -11156,7 +11161,10 @@ struct FilterResults {
bool gridsKnown = true;
if (context->getAuthorityFactory()) {
const auto gridsNeeded = op->gridsNeeded(
- context->getAuthorityFactory()->databaseContext());
+ context->getAuthorityFactory()->databaseContext(),
+ gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ KNOWN_AVAILABLE);
for (const auto &gridDesc : gridsNeeded) {
hasGrids = true;
if (gridAvailabilityUse ==
@@ -11278,6 +11286,7 @@ struct FilterResults {
CoordinateOperationPtr lastOp;
bool first = true;
+ const auto gridAvailabilityUse = context->getGridAvailabilityUse();
for (const auto &op : res) {
const auto curAccuracy = getAccuracy(op);
bool dummy = false;
@@ -11290,7 +11299,10 @@ struct FilterResults {
if (context->getAuthorityFactory()) {
const auto gridsNeeded = op->gridsNeeded(
- context->getAuthorityFactory()->databaseContext());
+ context->getAuthorityFactory()->databaseContext(),
+ gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ KNOWN_AVAILABLE);
for (const auto &gridDesc : gridsNeeded) {
curHasGrids = true;
curSetOfGrids.insert(gridDesc.shortName);
@@ -11603,6 +11615,7 @@ CoordinateOperationFactory::Private::findOpsInRegistryDirect(
buildCRSIds(sourceCRS, context, sourceIds);
buildCRSIds(targetCRS, context, targetIds);
+ const auto gridAvailabilityUse = context.context->getGridAvailabilityUse();
for (const auto &idSrc : sourceIds) {
const auto &srcAuthName = idSrc.first;
const auto &srcCode = idSrc.second;
@@ -11622,9 +11635,16 @@ CoordinateOperationFactory::Private::findOpsInRegistryDirect(
tmpAuthFactory->createFromCoordinateReferenceSystemCodes(
srcAuthName, srcCode, targetAuthName, targetCode,
context.context->getUsePROJAlternativeGridNames(),
- context.context->getGridAvailabilityUse() ==
+ gridAvailabilityUse ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::
+ DISCARD_OPERATION_IF_MISSING_GRID ||
+ gridAvailabilityUse ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::KNOWN_AVAILABLE,
+ gridAvailabilityUse ==
CoordinateOperationContext::GridAvailabilityUse::
- DISCARD_OPERATION_IF_MISSING_GRID,
+ KNOWN_AVAILABLE,
context.context->getDiscardSuperseded(), true, false,
context.extent1, context.extent2);
res.insert(res.end(), resTmp.begin(), resTmp.end());
@@ -11667,6 +11687,7 @@ CoordinateOperationFactory::Private::findOpsInRegistryDirectTo(
std::list<std::pair<std::string, std::string>> ids;
buildCRSIds(targetCRS, context, ids);
+ const auto gridAvailabilityUse = context.context->getGridAvailabilityUse();
for (const auto &id : ids) {
const auto &targetAuthName = id.first;
const auto &targetCode = id.second;
@@ -11680,9 +11701,15 @@ CoordinateOperationFactory::Private::findOpsInRegistryDirectTo(
auto res = tmpAuthFactory->createFromCoordinateReferenceSystemCodes(
std::string(), std::string(), targetAuthName, targetCode,
context.context->getUsePROJAlternativeGridNames(),
- context.context->getGridAvailabilityUse() ==
- CoordinateOperationContext::GridAvailabilityUse::
- DISCARD_OPERATION_IF_MISSING_GRID,
+
+ gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ DISCARD_OPERATION_IF_MISSING_GRID ||
+ gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ KNOWN_AVAILABLE,
+ gridAvailabilityUse == CoordinateOperationContext::
+ GridAvailabilityUse::KNOWN_AVAILABLE,
context.context->getDiscardSuperseded(), true, true,
context.extent1, context.extent2);
if (!res.empty()) {
@@ -11730,6 +11757,7 @@ CoordinateOperationFactory::Private::findsOpsInRegistryWithIntermediate(
buildCRSIds(sourceCRS, context, sourceIds);
buildCRSIds(targetCRS, context, targetIds);
+ const auto gridAvailabilityUse = context.context->getGridAvailabilityUse();
for (const auto &idSrc : sourceIds) {
const auto &srcAuthName = idSrc.first;
const auto &srcCode = idSrc.second;
@@ -11749,21 +11777,28 @@ CoordinateOperationFactory::Private::findsOpsInRegistryWithIntermediate(
std::vector<CoordinateOperationNNPtr> res;
if (useCreateBetweenGeodeticCRSWithDatumBasedIntermediates) {
- res = tmpAuthFactory
- ->createBetweenGeodeticCRSWithDatumBasedIntermediates(
- sourceCRS, srcAuthName, srcCode, targetCRS,
- targetAuthName, targetCode,
- context.context->getUsePROJAlternativeGridNames(),
- context.context->getGridAvailabilityUse() ==
- CoordinateOperationContext::
- GridAvailabilityUse::
- DISCARD_OPERATION_IF_MISSING_GRID,
- context.context->getDiscardSuperseded(),
- authFactory->getAuthority() != "any" &&
- authorities.size() > 1
- ? authorities
- : std::vector<std::string>(),
- context.extent1, context.extent2);
+ res =
+ tmpAuthFactory
+ ->createBetweenGeodeticCRSWithDatumBasedIntermediates(
+ sourceCRS, srcAuthName, srcCode, targetCRS,
+ targetAuthName, targetCode,
+ context.context->getUsePROJAlternativeGridNames(),
+ gridAvailabilityUse ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::
+ DISCARD_OPERATION_IF_MISSING_GRID ||
+ gridAvailabilityUse ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::KNOWN_AVAILABLE,
+ gridAvailabilityUse ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::KNOWN_AVAILABLE,
+ context.context->getDiscardSuperseded(),
+ authFactory->getAuthority() != "any" &&
+ authorities.size() > 1
+ ? authorities
+ : std::vector<std::string>(),
+ context.extent1, context.extent2);
} else {
io::AuthorityFactory::ObjectType intermediateObjectType =
io::AuthorityFactory::ObjectType::CRS;
@@ -11781,9 +11816,15 @@ CoordinateOperationFactory::Private::findsOpsInRegistryWithIntermediate(
res = tmpAuthFactory->createFromCRSCodesWithIntermediates(
srcAuthName, srcCode, targetAuthName, targetCode,
context.context->getUsePROJAlternativeGridNames(),
- context.context->getGridAvailabilityUse() ==
+ gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ DISCARD_OPERATION_IF_MISSING_GRID ||
+ gridAvailabilityUse ==
+ CoordinateOperationContext::GridAvailabilityUse::
+ KNOWN_AVAILABLE,
+ gridAvailabilityUse ==
CoordinateOperationContext::GridAvailabilityUse::
- DISCARD_OPERATION_IF_MISSING_GRID,
+ KNOWN_AVAILABLE,
context.context->getDiscardSuperseded(),
context.context->getIntermediateCRS(),
intermediateObjectType,
@@ -14211,15 +14252,21 @@ void CoordinateOperationFactory::Private::createOperationsCompoundToGeog(
componentsSrc[1],
targetCRS->promoteTo3D(std::string(), dbContext), context);
bool foundRegisteredTransformWithAllGridsAvailable = false;
+ const auto gridAvailabilityUse =
+ context.context->getGridAvailabilityUse();
const bool ignoreMissingGrids =
- context.context->getGridAvailabilityUse() ==
+ gridAvailabilityUse ==
CoordinateOperationContext::GridAvailabilityUse::
IGNORE_GRID_AVAILABILITY;
for (const auto &op : verticalTransforms) {
if (hasIdentifiers(op) && dbContext) {
bool missingGrid = false;
if (!ignoreMissingGrids) {
- const auto gridsNeeded = op->gridsNeeded(dbContext);
+ const auto gridsNeeded = op->gridsNeeded(
+ dbContext,
+ gridAvailabilityUse ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::KNOWN_AVAILABLE);
for (const auto &gridDesc : gridsNeeded) {
if (!gridDesc.available) {
missingGrid = true;
@@ -14248,7 +14295,11 @@ void CoordinateOperationFactory::Private::createOperationsCompoundToGeog(
if (hasIdentifiers(op) && dbContext) {
bool missingGrid = false;
if (!ignoreMissingGrids) {
- const auto gridsNeeded = op->gridsNeeded(dbContext);
+ const auto gridsNeeded = op->gridsNeeded(
+ dbContext,
+ gridAvailabilityUse ==
+ CoordinateOperationContext::
+ GridAvailabilityUse::KNOWN_AVAILABLE);
for (const auto &gridDesc : gridsNeeded) {
if (!gridDesc.available) {
missingGrid = true;
@@ -15079,8 +15130,9 @@ CoordinateOperationNNPtr PROJBasedOperation::_shallowClone() const {
// ---------------------------------------------------------------------------
-std::set<GridDescription> PROJBasedOperation::gridsNeeded(
- const io::DatabaseContextPtr &databaseContext) const {
+std::set<GridDescription>
+PROJBasedOperation::gridsNeeded(const io::DatabaseContextPtr &databaseContext,
+ bool considerKnownGridsAsAvailable) const {
std::set<GridDescription> res;
try {
@@ -15093,7 +15145,8 @@ std::set<GridDescription> PROJBasedOperation::gridsNeeded(
desc.shortName = shortName;
if (databaseContext) {
databaseContext->lookForGridInfo(
- desc.shortName, desc.fullName, desc.packageName, desc.url,
+ desc.shortName, considerKnownGridsAsAvailable,
+ desc.fullName, desc.packageName, desc.url,
desc.directDownload, desc.openLicense, desc.available);
}
res.insert(desc);
diff --git a/src/iso19111/factory.cpp b/src/iso19111/factory.cpp
index 0c7692ad..7d15172e 100644
--- a/src/iso19111/factory.cpp
+++ b/src/iso19111/factory.cpp
@@ -45,6 +45,8 @@
#include "proj/internal/lru_cache.hpp"
#include "proj/internal/tracing.hpp"
+#include "sqlite3_utils.hpp"
+
#include <cmath>
#include <cstdlib>
#include <cstring>
@@ -278,9 +280,7 @@ struct DatabaseContext::Private {
void registerFunctions();
#ifdef ENABLE_CUSTOM_LOCKLESS_VFS
- std::string thisNamePtr_{};
- sqlite3_vfs *vfs_{};
- bool createCustomVFS();
+ std::unique_ptr<SQLite3VFS> vfs_{};
#endif
Private(const Private &) = delete;
@@ -297,13 +297,6 @@ DatabaseContext::Private::~Private() {
assert(recLevel_ == 0);
closeDB();
-
-#ifdef ENABLE_CUSTOM_LOCKLESS_VFS
- if (vfs_) {
- sqlite3_vfs_unregister(vfs_);
- delete vfs_;
- }
-#endif
}
// ---------------------------------------------------------------------------
@@ -499,104 +492,12 @@ void DatabaseContext::Private::cache(const std::string &code,
// ---------------------------------------------------------------------------
-#ifdef ENABLE_CUSTOM_LOCKLESS_VFS
-
-typedef int (*ClosePtr)(sqlite3_file *);
-
-static int VFSClose(sqlite3_file *file) {
- sqlite3_vfs *defaultVFS = sqlite3_vfs_find(nullptr);
- assert(defaultVFS);
- ClosePtr defaultClosePtr;
- std::memcpy(&defaultClosePtr,
- reinterpret_cast<char *>(file) + defaultVFS->szOsFile,
- sizeof(ClosePtr));
- void *methods = const_cast<sqlite3_io_methods *>(file->pMethods);
- int ret = defaultClosePtr(file);
- std::free(methods);
- return ret;
-}
-
-// No-lock implementation
-static int VSFLock(sqlite3_file *, int) { return SQLITE_OK; }
-
-static int VSFUnlock(sqlite3_file *, int) { return SQLITE_OK; }
-
-static int VFSOpen(sqlite3_vfs *vfs, const char *name, sqlite3_file *file,
- int flags, int *outFlags) {
- sqlite3_vfs *defaultVFS = static_cast<sqlite3_vfs *>(vfs->pAppData);
- int ret = defaultVFS->xOpen(defaultVFS, name, file, flags, outFlags);
- if (ret == SQLITE_OK) {
- ClosePtr defaultClosePtr = file->pMethods->xClose;
- assert(defaultClosePtr);
- sqlite3_io_methods *methods = static_cast<sqlite3_io_methods *>(
- std::malloc(sizeof(sqlite3_io_methods)));
- if (!methods) {
- file->pMethods->xClose(file);
- return SQLITE_NOMEM;
- }
- memcpy(methods, file->pMethods, sizeof(sqlite3_io_methods));
- methods->xClose = VFSClose;
- methods->xLock = VSFLock;
- methods->xUnlock = VSFUnlock;
- file->pMethods = methods;
- // Save original xClose pointer at end of file structure
- std::memcpy(reinterpret_cast<char *>(file) + defaultVFS->szOsFile,
- &defaultClosePtr, sizeof(ClosePtr));
- }
- return ret;
-}
-
-static int VFSAccess(sqlite3_vfs *vfs, const char *zName, int flags,
- int *pResOut) {
- sqlite3_vfs *defaultVFS = static_cast<sqlite3_vfs *>(vfs->pAppData);
- // Do not bother stat'ing for journal or wal files
- if (std::strstr(zName, "-journal") || std::strstr(zName, "-wal")) {
- *pResOut = false;
- return SQLITE_OK;
- }
- return defaultVFS->xAccess(defaultVFS, zName, flags, pResOut);
-}
-
-// ---------------------------------------------------------------------------
-
-bool DatabaseContext::Private::createCustomVFS() {
-
- sqlite3_vfs *defaultVFS = sqlite3_vfs_find(nullptr);
- assert(defaultVFS);
-
- std::ostringstream buffer;
- buffer << this;
- thisNamePtr_ = buffer.str();
-
- vfs_ = new sqlite3_vfs();
- vfs_->iVersion = 1;
- vfs_->szOsFile = defaultVFS->szOsFile + sizeof(ClosePtr);
- vfs_->mxPathname = defaultVFS->mxPathname;
- vfs_->zName = thisNamePtr_.c_str();
- vfs_->pAppData = defaultVFS;
- vfs_->xOpen = VFSOpen;
- vfs_->xDelete = defaultVFS->xDelete;
- vfs_->xAccess = VFSAccess;
- vfs_->xFullPathname = defaultVFS->xFullPathname;
- vfs_->xDlOpen = defaultVFS->xDlOpen;
- vfs_->xDlError = defaultVFS->xDlError;
- vfs_->xDlSym = defaultVFS->xDlSym;
- vfs_->xDlClose = defaultVFS->xDlClose;
- vfs_->xRandomness = defaultVFS->xRandomness;
- vfs_->xSleep = defaultVFS->xSleep;
- vfs_->xCurrentTime = defaultVFS->xCurrentTime;
- vfs_->xGetLastError = defaultVFS->xGetLastError;
- vfs_->xCurrentTimeInt64 = defaultVFS->xCurrentTimeInt64;
- return sqlite3_vfs_register(vfs_, false) == SQLITE_OK;
-}
-
-#endif // ENABLE_CUSTOM_LOCKLESS_VFS
-
-// ---------------------------------------------------------------------------
-
void DatabaseContext::Private::open(const std::string &databasePath,
PJ_CONTEXT *ctx) {
- setPjCtxt(ctx ? ctx : pj_get_default_ctx());
+ if (!ctx) {
+ ctx = pj_get_default_ctx();
+ }
+ setPjCtxt(ctx);
std::string path(databasePath);
if (path.empty()) {
path.resize(2048);
@@ -608,18 +509,23 @@ void DatabaseContext::Private::open(const std::string &databasePath,
}
}
- if (
+ std::string vfsName;
#ifdef ENABLE_CUSTOM_LOCKLESS_VFS
- !createCustomVFS() ||
+ if (ctx->custom_sqlite3_vfs_name.empty()) {
+ vfs_ = SQLite3VFS::create(false, true, true);
+ if (vfs_ == nullptr) {
+ throw FactoryException("Open of " + path + " failed");
+ }
+ vfsName = vfs_->name();
+ } else
#endif
- sqlite3_open_v2(path.c_str(), &sqlite_handle_,
+ {
+ vfsName = ctx->custom_sqlite3_vfs_name;
+ }
+ if (sqlite3_open_v2(path.c_str(), &sqlite_handle_,
SQLITE_OPEN_READONLY | SQLITE_OPEN_NOMUTEX,
-#ifdef ENABLE_CUSTOM_LOCKLESS_VFS
- thisNamePtr_.c_str()
-#else
- nullptr
-#endif
- ) != SQLITE_OK ||
+ vfsName.empty() ? nullptr : vfsName.c_str()) !=
+ SQLITE_OK ||
!sqlite_handle_) {
throw FactoryException("Open of " + path + " failed");
}
@@ -1021,14 +927,14 @@ bool DatabaseContext::lookForGridAlternative(const std::string &officialName,
// ---------------------------------------------------------------------------
-bool DatabaseContext::lookForGridInfo(const std::string &projFilename,
- std::string &fullFilename,
- std::string &packageName,
- std::string &url, bool &directDownload,
- bool &openLicense,
- bool &gridAvailable) const {
+bool DatabaseContext::lookForGridInfo(
+ const std::string &projFilename, bool considerKnownGridsAsAvailable,
+ std::string &fullFilename, std::string &packageName, std::string &url,
+ bool &directDownload, bool &openLicense, bool &gridAvailable) const {
Private::GridInfoCache info;
- if (d->getGridInfoFromCache(projFilename, info)) {
+ const std::string key(projFilename +
+ (considerKnownGridsAsAvailable ? "true" : "false"));
+ if (d->getGridInfoFromCache(key, info)) {
fullFilename = info.fullFilename;
packageName = info.packageName;
url = info.url;
@@ -1044,16 +950,20 @@ bool DatabaseContext::lookForGridInfo(const std::string &projFilename,
openLicense = false;
directDownload = false;
- fullFilename.resize(2048);
- if (d->pjCtxt() == nullptr) {
- d->setPjCtxt(pj_get_default_ctx());
+ if (considerKnownGridsAsAvailable) {
+ fullFilename = projFilename;
+ } else {
+ fullFilename.resize(2048);
+ if (d->pjCtxt() == nullptr) {
+ d->setPjCtxt(pj_get_default_ctx());
+ }
+ int errno_before = proj_context_errno(d->pjCtxt());
+ gridAvailable =
+ pj_find_file(d->pjCtxt(), projFilename.c_str(), &fullFilename[0],
+ fullFilename.size() - 1) != 0;
+ proj_context_errno_set(d->pjCtxt(), errno_before);
+ fullFilename.resize(strlen(fullFilename.c_str()));
}
- int errno_before = proj_context_errno(d->pjCtxt());
- gridAvailable =
- pj_find_file(d->pjCtxt(), projFilename.c_str(), &fullFilename[0],
- fullFilename.size() - 1) != 0;
- proj_context_errno_set(d->pjCtxt(), errno_before);
- fullFilename.resize(strlen(fullFilename.c_str()));
auto res =
d->run("SELECT "
@@ -1077,6 +987,10 @@ bool DatabaseContext::lookForGridInfo(const std::string &projFilename,
openLicense = (row[3].empty() ? row[4] : row[3]) == "1";
directDownload = (row[5].empty() ? row[6] : row[5]) == "1";
+ if (considerKnownGridsAsAvailable && !packageName.empty()) {
+ gridAvailable = true;
+ }
+
info.fullFilename = fullFilename;
info.packageName = packageName;
info.url = url;
@@ -1085,7 +999,7 @@ bool DatabaseContext::lookForGridInfo(const std::string &projFilename,
}
info.gridAvailable = gridAvailable;
info.found = ret;
- d->cache(projFilename, info);
+ d->cache(key, info);
return ret;
}
@@ -1324,8 +1238,8 @@ struct AuthorityFactory::Private {
return AuthorityFactory::create(context_, auth_name);
}
- bool
- rejectOpDueToMissingGrid(const operation::CoordinateOperationNNPtr &op);
+ bool rejectOpDueToMissingGrid(const operation::CoordinateOperationNNPtr &op,
+ bool considerKnownGridsAsAvailable);
UnitOfMeasure createUnitOfMeasure(const std::string &auth_name,
const std::string &code);
@@ -1452,8 +1366,10 @@ util::PropertyMap AuthorityFactory::Private::createProperties(
// ---------------------------------------------------------------------------
bool AuthorityFactory::Private::rejectOpDueToMissingGrid(
- const operation::CoordinateOperationNNPtr &op) {
- for (const auto &gridDesc : op->gridsNeeded(context())) {
+ const operation::CoordinateOperationNNPtr &op,
+ bool considerKnownGridsAsAvailable) {
+ for (const auto &gridDesc :
+ op->gridsNeeded(context(), considerKnownGridsAsAvailable)) {
if (!gridDesc.available) {
return true;
}
@@ -3449,7 +3365,7 @@ AuthorityFactory::createFromCoordinateReferenceSystemCodes(
const std::string &sourceCRSCode, const std::string &targetCRSCode) const {
return createFromCoordinateReferenceSystemCodes(
d->authority(), sourceCRSCode, d->authority(), targetCRSCode, false,
- false, false);
+ false, false, false);
}
// ---------------------------------------------------------------------------
@@ -3478,6 +3394,8 @@ AuthorityFactory::createFromCoordinateReferenceSystemCodes(
* should be substituted to the official grid names.
* @param discardIfMissingGrid Whether coordinate operations that reference
* missing grids should be removed from the result set.
+ * @param considerKnownGridsAsAvailable Whether known grids should be considered
+ * as available (typically when network is enabled).
* @param discardSuperseded Whether cordinate operations that are superseded
* (but not deprecated) should be removed from the result set.
* @param tryReverseOrder whether to search in the reverse order too (and thus
@@ -3498,8 +3416,8 @@ AuthorityFactory::createFromCoordinateReferenceSystemCodes(
const std::string &sourceCRSAuthName, const std::string &sourceCRSCode,
const std::string &targetCRSAuthName, const std::string &targetCRSCode,
bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
- bool discardSuperseded, bool tryReverseOrder,
- bool reportOnlyIntersectingTransformations,
+ bool considerKnownGridsAsAvailable, bool discardSuperseded,
+ bool tryReverseOrder, bool reportOnlyIntersectingTransformations,
const metadata::ExtentPtr &intersectingExtent1,
const metadata::ExtentPtr &intersectingExtent2) const {
@@ -3510,6 +3428,7 @@ AuthorityFactory::createFromCoordinateReferenceSystemCodes(
cacheKey += targetCRSCode;
cacheKey += (usePROJAlternativeGridNames ? '1' : '0');
cacheKey += (discardIfMissingGrid ? '1' : '0');
+ cacheKey += (considerKnownGridsAsAvailable ? '1' : '0');
cacheKey += (discardSuperseded ? '1' : '0');
cacheKey += (tryReverseOrder ? '1' : '0');
cacheKey += (reportOnlyIntersectingTransformations ? '1' : '0');
@@ -3748,7 +3667,8 @@ AuthorityFactory::createFromCoordinateReferenceSystemCodes(
target_crs_code != targetCRSCode))) {
op = op->inverse();
}
- if (!discardIfMissingGrid || !d->rejectOpDueToMissingGrid(op)) {
+ if (!discardIfMissingGrid ||
+ !d->rejectOpDueToMissingGrid(op, considerKnownGridsAsAvailable)) {
list.emplace_back(op);
}
}
@@ -3813,6 +3733,8 @@ static bool useIrrelevantPivot(const operation::CoordinateOperationNNPtr &op,
* should be substituted to the official grid names.
* @param discardIfMissingGrid Whether coordinate operations that reference
* missing grids should be removed from the result set.
+ * @param considerKnownGridsAsAvailable Whether known grids should be considered
+ * as available (typically when network is enabled).
* @param discardSuperseded Whether cordinate operations that are superseded
* (but not deprecated) should be removed from the result set.
* @param intermediateCRSAuthCodes List of (auth_name, code) of CRS that can be
@@ -3841,7 +3763,7 @@ AuthorityFactory::createFromCRSCodesWithIntermediates(
const std::string &sourceCRSAuthName, const std::string &sourceCRSCode,
const std::string &targetCRSAuthName, const std::string &targetCRSCode,
bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
- bool discardSuperseded,
+ bool considerKnownGridsAsAvailable, bool discardSuperseded,
const std::vector<std::pair<std::string, std::string>>
&intermediateCRSAuthCodes,
ObjectType allowedIntermediateObjectType,
@@ -4289,7 +4211,8 @@ AuthorityFactory::createFromCRSCodesWithIntermediates(
std::vector<operation::CoordinateOperationNNPtr> list;
for (const auto &op : listTmp) {
- if (!discardIfMissingGrid || !d->rejectOpDueToMissingGrid(op)) {
+ if (!discardIfMissingGrid ||
+ !d->rejectOpDueToMissingGrid(op, considerKnownGridsAsAvailable)) {
list.emplace_back(op);
}
}
@@ -4307,7 +4230,8 @@ AuthorityFactory::createBetweenGeodeticCRSWithDatumBasedIntermediates(
const std::string &sourceCRSCode, const crs::CRSNNPtr &targetCRS,
const std::string &targetCRSAuthName, const std::string &targetCRSCode,
bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
- bool discardSuperseded, const std::vector<std::string> &allowedAuthorities,
+ bool considerKnownGridsAsAvailable, bool discardSuperseded,
+ const std::vector<std::string> &allowedAuthorities,
const metadata::ExtentPtr &intersectingExtent1,
const metadata::ExtentPtr &intersectingExtent2) const {
@@ -4890,7 +4814,8 @@ AuthorityFactory::createBetweenGeodeticCRSWithDatumBasedIntermediates(
std::vector<operation::CoordinateOperationNNPtr> list;
for (const auto &op : listTmp) {
- if (!discardIfMissingGrid || !d->rejectOpDueToMissingGrid(op)) {
+ if (!discardIfMissingGrid ||
+ !d->rejectOpDueToMissingGrid(op, considerKnownGridsAsAvailable)) {
list.emplace_back(op);
}
}
diff --git a/src/lib_proj.cmake b/src/lib_proj.cmake
index 2e470683..704ece3d 100644
--- a/src/lib_proj.cmake
+++ b/src/lib_proj.cmake
@@ -198,6 +198,7 @@ set(SRC_LIBPROJ_TRANSFORMATIONS
transformations/horner.cpp
transformations/molodensky.cpp
transformations/vgridshift.cpp
+ transformations/xyzgridshift.cpp
)
set(SRC_LIBPROJ_ISO19111
@@ -219,8 +220,6 @@ set(SRC_LIBPROJ_CORE
4D_api.cpp
aasincos.cpp
adjlon.cpp
- apply_gridshift.cpp
- apply_vgridshift.cpp
auth.cpp
ctx.cpp
datum_set.cpp
@@ -234,13 +233,9 @@ set(SRC_LIBPROJ_CORE
fileapi.cpp
fwd.cpp
gauss.cpp
- gc_reader.cpp
geocent.cpp
geocent.h
geodesic.c
- gridcatalog.cpp
- gridinfo.cpp
- gridlist.cpp
init.cpp
initcache.cpp
internal.cpp
@@ -251,10 +246,6 @@ set(SRC_LIBPROJ_CORE
mlfn.cpp
msfn.cpp
mutex.cpp
- nad_cvt.cpp
- nad_init.cpp
- nad_intr.cpp
- open_lib.cpp
param.cpp
phi2.cpp
pipeline.cpp
@@ -285,6 +276,13 @@ set(SRC_LIBPROJ_CORE
proj_json_streaming_writer.hpp
proj_json_streaming_writer.cpp
tracing.cpp
+ grids.hpp
+ grids.cpp
+ filemanager.hpp
+ filemanager.cpp
+ networkfilemanager.cpp
+ sqlite3_utils.hpp
+ sqlite3_utils.cpp
${CMAKE_CURRENT_BINARY_DIR}/proj_config.h
)
@@ -348,6 +346,10 @@ target_compile_options(${PROJ_CORE_TARGET}
PRIVATE $<$<COMPILE_LANGUAGE:CXX>:${PROJ_CXX_WARN_FLAGS}>
)
+if(MSVC OR MINGW)
+ target_compile_definitions(${PROJ_CORE_TARGET} PRIVATE -DNOMINMAX)
+endif()
+
# Tell Intel compiler to do arithmetic accurately. This is needed to stop the
# compiler from ignoring parentheses in expressions like (a + b) + c and from
# simplifying 0.0 + x to x (which is wrong if x = -0.0).
@@ -419,9 +421,19 @@ if(USE_THREAD AND Threads_FOUND AND CMAKE_USE_PTHREADS_INIT)
target_link_libraries(${PROJ_CORE_TARGET} ${CMAKE_THREAD_LIBS_INIT})
endif()
-include_directories(${SQLITE3_INCLUDE_DIR})
+target_include_directories(${PROJ_CORE_TARGET} PRIVATE ${SQLITE3_INCLUDE_DIR})
target_link_libraries(${PROJ_CORE_TARGET} ${SQLITE3_LIBRARY})
+if(NOT DISABLE_TIFF)
+ target_include_directories(${PROJ_CORE_TARGET} PRIVATE ${TIFF_INCLUDE_DIR})
+ target_link_libraries(${PROJ_CORE_TARGET} ${TIFF_LIBRARY})
+endif()
+
+if(CURL_FOUND)
+ target_include_directories(${PROJ_CORE_TARGET} PRIVATE ${CURL_INCLUDE_DIR})
+ target_link_libraries(${PROJ_CORE_TARGET} ${CURL_LIBRARY})
+endif()
+
if(MSVC AND BUILD_LIBPROJ_SHARED)
target_compile_definitions(${PROJ_CORE_TARGET}
PRIVATE PROJ_MSVC_DLL_EXPORT=1)
diff --git a/src/malloc.cpp b/src/malloc.cpp
index 393437e3..9ac28546 100644
--- a/src/malloc.cpp
+++ b/src/malloc.cpp
@@ -48,6 +48,10 @@
#include "proj.h"
#include "proj_internal.h"
+#include "grids.hpp"
+#include "filemanager.hpp"
+
+using namespace NS_PROJ;
/**********************************************************************/
void *pj_malloc(size_t size) {
@@ -225,10 +229,8 @@ PJ *pj_default_destructor (PJ *P, int errlev) { /* Destructor */
pj_dealloc(P->def_spherification);
pj_dealloc(P->def_ellps);
- /* free grid lists */
- pj_dealloc( P->gridlist );
- pj_dealloc( P->vgridlist_geoid );
- pj_dealloc( P->catalog_name );
+ delete static_cast<ListOfHGrids*>(P->hgrids_legacy);
+ delete static_cast<ListOfVGrids*>(P->vgrids_legacy);
/* We used to call pj_dalloc( P->catalog ), but this will leak */
/* memory. The safe way to clear catalog and grid is to call */
@@ -261,4 +263,5 @@ void proj_cleanup() {
/*****************************************************************************/
pj_clear_initcache();
pj_deallocate_grids();
+ FileManager::clearMemoryCache();
}
diff --git a/src/nad_cvt.cpp b/src/nad_cvt.cpp
deleted file mode 100644
index e8b8e9b7..00000000
--- a/src/nad_cvt.cpp
+++ /dev/null
@@ -1,98 +0,0 @@
-#define PJ_LIB__
-
-#include <stdio.h>
-#include <stdlib.h>
-
-#include "proj.h"
-#include "proj_internal.h"
-#include <math.h>
-
-#include <limits>
-
-#define MAX_ITERATIONS 10
-#define TOL 1e-12
-
-PJ_LP nad_cvt(projCtx ctx, PJ_LP in, int inverse, struct CTABLE *ct, int grid_count, PJ_GRIDINFO **tables) {
- PJ_LP t, tb,del, dif;
- int i = MAX_ITERATIONS;
- const double toltol = TOL*TOL;
-
- if (in.lam == HUGE_VAL)
- return in;
-
- /* normalize input to ll origin */
- tb = in;
- tb.lam -= ct->ll.lam;
- tb.phi -= ct->ll.phi;
- tb.lam = adjlon (tb.lam - M_PI) + M_PI;
-
- t = nad_intr (tb, ct);
- if (t.lam == HUGE_VAL)
- return t;
-
- if (!inverse) {
- in.lam -= t.lam;
- in.phi += t.phi;
- return in;
- }
-
- t.lam = tb.lam + t.lam;
- t.phi = tb.phi - t.phi;
-
- do {
- del = nad_intr(t, ct);
-
- /* In case we are (or have switched) on the null grid, exit now */
- if( del.lam == 0 && del.phi == 0 )
- break;
-
- /* We can possibly go outside of the initial guessed grid, so try */
- /* to fetch a new grid into which iterate... */
- if (del.lam == HUGE_VAL)
- {
- if( grid_count == 0 )
- break;
- PJ_LP lp;
- lp.lam = t.lam + ct->ll.lam;
- lp.phi = t.phi + ct->ll.phi;
- auto newCt = find_ctable(ctx, lp, grid_count, tables);
- if( newCt == nullptr || newCt == ct )
- break;
- pj_log(ctx, PJ_LOG_DEBUG_MINOR, "Switching from grid %s to grid %s",
- ct->id,
- newCt->id);
- ct = newCt;
- t.lam = lp.lam - ct->ll.lam;
- t.phi = lp.phi - ct->ll.phi;
- tb = in;
- tb.lam -= ct->ll.lam;
- tb.phi -= ct->ll.phi;
- tb.lam = adjlon (tb.lam - M_PI) + M_PI;
- dif.lam = std::numeric_limits<double>::max();
- dif.phi = std::numeric_limits<double>::max();
- continue;
- }
-
- dif.lam = t.lam - del.lam - tb.lam;
- dif.phi = t.phi + del.phi - tb.phi;
- t.lam -= dif.lam;
- t.phi -= dif.phi;
-
- } while (--i && (dif.lam*dif.lam + dif.phi*dif.phi > toltol)); /* prob. slightly faster than hypot() */
-
- if (i==0) {
- /* If we had access to a context, this should go through pj_log, and we should set ctx->errno */
- if (getenv ("PROJ_DEBUG"))
- fprintf( stderr, "Inverse grid shift iterator failed to converge.\n" );
- t.lam = t.phi = HUGE_VAL;
- return t;
- }
-
- /* and again: pj_log and ctx->errno */
- if (del.lam==HUGE_VAL && getenv ("PROJ_DEBUG"))
- fprintf (stderr, "Inverse grid shift iteration failed, presumably at grid edge.\nUsing first approximation.\n");
-
- in.lam = adjlon (t.lam + ct->ll.lam);
- in.phi = t.phi + ct->ll.phi;
- return in;
-}
diff --git a/src/nad_init.cpp b/src/nad_init.cpp
deleted file mode 100644
index 315318be..00000000
--- a/src/nad_init.cpp
+++ /dev/null
@@ -1,308 +0,0 @@
-/******************************************************************************
- * Project: PROJ.4
- * Purpose: Load datum shift files into memory.
- * Author: Frank Warmerdam, warmerdam@pobox.com
- *
- ******************************************************************************
- * Copyright (c) 2000, Frank Warmerdam
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included
- * in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- *****************************************************************************/
-
-#define PJ_LIB__
-
-#include <errno.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "proj.h"
-#include "proj_internal.h"
-
-/************************************************************************/
-/* swap_words() */
-/* */
-/* Convert the byte order of the given word(s) in place. */
-/************************************************************************/
-
-static const int byte_order_test = 1;
-#define IS_LSB (((const unsigned char *) (&byte_order_test))[0] == 1)
-
-static void swap_words( void *data_in, int word_size, int word_count )
-
-{
- int word;
- unsigned char *data = (unsigned char *) data_in;
-
- for( word = 0; word < word_count; word++ )
- {
- int i;
-
- for( i = 0; i < word_size/2; i++ )
- {
- unsigned char t;
-
- t = data[i];
- data[i] = data[word_size-i-1];
- data[word_size-i-1] = t;
- }
-
- data += word_size;
- }
-}
-
-/************************************************************************/
-/* nad_ctable_load() */
-/* */
-/* Load the data portion of a ctable formatted grid. */
-/************************************************************************/
-
-int nad_ctable_load( projCtx ctx, struct CTABLE *ct, struct projFileAPI_t* fileapi )
-
-{
- PAFile fid = (PAFile)fileapi;
- size_t a_size;
-
- pj_ctx_fseek( ctx, fid, sizeof(struct CTABLE), SEEK_SET );
-
- /* read all the actual shift values */
- a_size = ct->lim.lam * ct->lim.phi;
- ct->cvs = (FLP *) pj_malloc(sizeof(FLP) * a_size);
- if( ct->cvs == nullptr
- || pj_ctx_fread(ctx, ct->cvs, sizeof(FLP), a_size, fid) != a_size )
- {
- pj_dalloc( ct->cvs );
- ct->cvs = nullptr;
-
- pj_log( ctx, PJ_LOG_ERROR,
- "ctable loading failed on fread() - binary incompatible?" );
- pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- return 0;
- }
-
- return 1;
-}
-
-/************************************************************************/
-/* nad_ctable_init() */
-/* */
-/* Read the header portion of a "ctable" format grid. */
-/************************************************************************/
-
-struct CTABLE *nad_ctable_init( projCtx ctx, struct projFileAPI_t* fileapi )
-{
- PAFile fid = (PAFile)fileapi;
- struct CTABLE *ct;
- int id_end;
-
- /* read the table header */
- ct = (struct CTABLE *) pj_malloc(sizeof(struct CTABLE));
- if( ct == nullptr
- || pj_ctx_fread( ctx, ct, sizeof(struct CTABLE), 1, fid ) != 1 )
- {
- pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- pj_dalloc( ct );
- return nullptr;
- }
-
- /* do some minimal validation to ensure the structure isn't corrupt */
- if( ct->lim.lam < 1 || ct->lim.lam > 100000
- || ct->lim.phi < 1 || ct->lim.phi > 100000 )
- {
- pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- pj_dalloc( ct );
- return nullptr;
- }
-
- /* trim white space and newlines off id */
- for( id_end = (int)strlen(ct->id)-1; id_end > 0; id_end-- )
- {
- if( ct->id[id_end] == '\n' || ct->id[id_end] == ' ' )
- ct->id[id_end] = '\0';
- else
- break;
- }
-
- ct->cvs = nullptr;
-
- return ct;
-}
-
-/************************************************************************/
-/* nad_ctable2_load() */
-/* */
-/* Load the data portion of a ctable2 formatted grid. */
-/************************************************************************/
-
-int nad_ctable2_load( projCtx ctx, struct CTABLE *ct, struct projFileAPI_t* fileapi )
-
-{
- PAFile fid = (PAFile)fileapi;
- size_t a_size;
-
- pj_ctx_fseek( ctx, fid, 160, SEEK_SET );
-
- /* read all the actual shift values */
- a_size = ct->lim.lam * ct->lim.phi;
- ct->cvs = (FLP *) pj_malloc(sizeof(FLP) * a_size);
- if( ct->cvs == nullptr
- || pj_ctx_fread(ctx, ct->cvs, sizeof(FLP), a_size, fid) != a_size )
- {
- pj_dalloc( ct->cvs );
- ct->cvs = nullptr;
-
- if( getenv("PROJ_DEBUG") != nullptr )
- {
- fprintf( stderr,
- "ctable2 loading failed on fread() - binary incompatible?\n" );
- }
-
- pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- return 0;
- }
-
- if( !IS_LSB )
- {
- swap_words( ct->cvs, 4, (int)a_size * 2 );
- }
-
- return 1;
-}
-
-/************************************************************************/
-/* nad_ctable2_init() */
-/* */
-/* Read the header portion of a "ctable2" format grid. */
-/************************************************************************/
-
-struct CTABLE *nad_ctable2_init( projCtx ctx, struct projFileAPI_t* fileapi )
-{
- PAFile fid = (PAFile)fileapi;
- struct CTABLE *ct;
- int id_end;
- char header[160];
-
- if( pj_ctx_fread( ctx, header, sizeof(header), 1, fid ) != 1 )
- {
- pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- return nullptr;
- }
-
- if( !IS_LSB )
- {
- swap_words( header + 96, 8, 4 );
- swap_words( header + 128, 4, 2 );
- }
-
- if( strncmp(header,"CTABLE V2",9) != 0 )
- {
- pj_log( ctx, PJ_LOG_ERROR, "ctable2 - wrong header!" );
- pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- return nullptr;
- }
-
- /* read the table header */
- ct = (struct CTABLE *) pj_malloc(sizeof(struct CTABLE));
- if( ct == nullptr )
- {
- pj_ctx_set_errno( ctx, ENOMEM );
- return nullptr;
- }
-
- memcpy( ct->id, header + 16, 80 );
- memcpy( &ct->ll.lam, header + 96, 8 );
- memcpy( &ct->ll.phi, header + 104, 8 );
- memcpy( &ct->del.lam, header + 112, 8 );
- memcpy( &ct->del.phi, header + 120, 8 );
- memcpy( &ct->lim.lam, header + 128, 4 );
- memcpy( &ct->lim.phi, header + 132, 4 );
-
- /* do some minimal validation to ensure the structure isn't corrupt */
- if( ct->lim.lam < 1 || ct->lim.lam > 100000
- || ct->lim.phi < 1 || ct->lim.phi > 100000 )
- {
- pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID );
- pj_dalloc( ct );
- return nullptr;
- }
-
- /* trim white space and newlines off id */
- for( id_end = (int)strlen(ct->id)-1; id_end > 0; id_end-- )
- {
- if( ct->id[id_end] == '\n' || ct->id[id_end] == ' ' )
- ct->id[id_end] = '\0';
- else
- break;
- }
-
- ct->cvs = nullptr;
-
- return ct;
-}
-
-/************************************************************************/
-/* nad_init() */
-/* */
-/* Read a datum shift file in any of the supported binary formats. */
-/************************************************************************/
-
-struct CTABLE *nad_init(projCtx ctx, char *name)
-{
- struct CTABLE *ct;
- PAFile fid;
-
- ctx->last_errno = 0;
-
-/* -------------------------------------------------------------------- */
-/* Open the file using the usual search rules. */
-/* -------------------------------------------------------------------- */
- if (!(fid = pj_open_lib(ctx, name, "rb"))) {
- return nullptr;
- }
-
- ct = nad_ctable_init( ctx, (struct projFileAPI_t*)fid );
- if( ct != nullptr )
- {
- if( !nad_ctable_load( ctx, ct, (struct projFileAPI_t*)fid ) )
- {
- nad_free( ct );
- ct = nullptr;
- }
- }
-
- pj_ctx_fclose(ctx, fid);
- return ct;
-}
-
-/************************************************************************/
-/* nad_free() */
-/* */
-/* Free a CTABLE grid shift structure produced by nad_init(). */
-/************************************************************************/
-
-void nad_free(struct CTABLE *ct)
-{
- if (ct) {
- if( ct->cvs != nullptr )
- pj_dalloc(ct->cvs);
-
- pj_dalloc(ct);
- }
-}
diff --git a/src/nad_intr.cpp b/src/nad_intr.cpp
deleted file mode 100644
index 36ab0a99..00000000
--- a/src/nad_intr.cpp
+++ /dev/null
@@ -1,67 +0,0 @@
-/* Determine nad table correction value */
-#define PJ_LIB__
-#include "proj.h"
-#include "proj_internal.h"
-#include <math.h>
-
-PJ_LP nad_intr(PJ_LP t, struct CTABLE *ct) {
- PJ_LP val, frct;
- ILP indx;
- double m00, m10, m01, m11;
- FLP *f00, *f10, *f01, *f11;
- long index;
- int in;
-
- t.lam /= ct->del.lam;
- indx.lam = isnan(t.lam) ? 0 : (pj_int32)lround(floor(t.lam));
- t.phi /= ct->del.phi;
- indx.phi = isnan(t.phi) ? 0 : (pj_int32)lround(floor(t.phi));
-
- frct.lam = t.lam - indx.lam;
- frct.phi = t.phi - indx.phi;
- val.lam = val.phi = HUGE_VAL;
- if (indx.lam < 0) {
- if (indx.lam == -1 && frct.lam > 0.99999999999) {
- ++indx.lam;
- frct.lam = 0.;
- } else
- return val;
- } else if ((in = indx.lam + 1) >= ct->lim.lam) {
- if (in == ct->lim.lam && frct.lam < 1e-11) {
- --indx.lam;
- frct.lam = 1.;
- } else
- return val;
- }
- if (indx.phi < 0) {
- if (indx.phi == -1 && frct.phi > 0.99999999999) {
- ++indx.phi;
- frct.phi = 0.;
- } else
- return val;
- } else if ((in = indx.phi + 1) >= ct->lim.phi) {
- if (in == ct->lim.phi && frct.phi < 1e-11) {
- --indx.phi;
- frct.phi = 1.;
- } else
- return val;
- }
- index = indx.phi * ct->lim.lam + indx.lam;
- f00 = ct->cvs + index++;
- f10 = ct->cvs + index;
- index += ct->lim.lam;
- f11 = ct->cvs + index--;
- f01 = ct->cvs + index;
- m11 = m10 = frct.lam;
- m00 = m01 = 1. - frct.lam;
- m11 *= frct.phi;
- m01 *= frct.phi;
- frct.phi = 1. - frct.phi;
- m00 *= frct.phi;
- m10 *= frct.phi;
- val.lam = m00 * f00->lam + m10 * f10->lam +
- m01 * f01->lam + m11 * f11->lam;
- val.phi = m00 * f00->phi + m10 * f10->phi +
- m01 * f01->phi + m11 * f11->phi;
- return val;
-}
diff --git a/src/networkfilemanager.cpp b/src/networkfilemanager.cpp
new file mode 100644
index 00000000..64969d0f
--- /dev/null
+++ b/src/networkfilemanager.cpp
@@ -0,0 +1,2544 @@
+/******************************************************************************
+ * Project: PROJ
+ * Purpose: Functionality related to network access and caching
+ * Author: Even Rouault, <even.rouault at spatialys.com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2019-2020, Even Rouault, <even.rouault at spatialys.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *****************************************************************************/
+
+#ifndef FROM_PROJ_CPP
+#define FROM_PROJ_CPP
+#endif
+#define LRU11_DO_NOT_DEFINE_OUT_OF_CLASS_METHODS
+
+#include <stdlib.h>
+
+#include <algorithm>
+#include <limits>
+#include <string>
+
+#include "filemanager.hpp"
+#include "proj.h"
+#include "proj/internal/internal.hpp"
+#include "proj/internal/lru_cache.hpp"
+#include "proj_internal.h"
+#include "sqlite3_utils.hpp"
+
+#ifdef __MINGW32__
+// mingw32-win32 doesn't implement std::mutex
+namespace {
+class MyMutex {
+ public:
+ // cppcheck-suppress functionStatic
+ void lock() { pj_acquire_lock(); }
+ // cppcheck-suppress functionStatic
+ void unlock() { pj_release_lock(); }
+};
+}
+#else
+#include <mutex>
+#define MyMutex std::mutex
+#endif
+
+#ifdef CURL_ENABLED
+#include <curl/curl.h>
+#include <sqlite3.h> // for sqlite3_snprintf
+#endif
+
+#include <sys/stat.h>
+
+#ifdef _WIN32
+#include <shlobj.h>
+#else
+#include <sys/types.h>
+#include <unistd.h>
+#endif
+
+#if defined(_WIN32)
+#include <windows.h>
+#elif defined(__MACH__) && defined(__APPLE__)
+#include <mach-o/dyld.h>
+#elif defined(__FreeBSD__)
+#include <sys/sysctl.h>
+#include <sys/types.h>
+#endif
+
+#include <time.h>
+
+//! @cond Doxygen_Suppress
+
+#define STR_HELPER(x) #x
+#define STR(x) STR_HELPER(x)
+
+using namespace NS_PROJ::internal;
+
+NS_PROJ_START
+
+// ---------------------------------------------------------------------------
+
+static void sleep_ms(int ms) {
+#ifdef _WIN32
+ Sleep(ms);
+#else
+ usleep(ms * 1000);
+#endif
+}
+
+// ---------------------------------------------------------------------------
+
+constexpr size_t DOWNLOAD_CHUNK_SIZE = 16 * 1024;
+constexpr int MAX_CHUNKS = 64;
+
+struct FileProperties {
+ unsigned long long size = 0;
+ time_t lastChecked = 0;
+ std::string lastModified{};
+ std::string etag{};
+};
+
+class NetworkChunkCache {
+ public:
+ void insert(PJ_CONTEXT *ctx, const std::string &url,
+ unsigned long long chunkIdx, std::vector<unsigned char> &&data);
+
+ std::shared_ptr<std::vector<unsigned char>>
+ get(PJ_CONTEXT *ctx, const std::string &url, unsigned long long chunkIdx);
+
+ std::shared_ptr<std::vector<unsigned char>> get(PJ_CONTEXT *ctx,
+ const std::string &url,
+ unsigned long long chunkIdx,
+ FileProperties &props);
+
+ void clearMemoryCache();
+
+ static void clearDiskChunkCache(PJ_CONTEXT *ctx);
+
+ private:
+ struct Key {
+ std::string url;
+ unsigned long long chunkIdx;
+
+ Key(const std::string &urlIn, unsigned long long chunkIdxIn)
+ : url(urlIn), chunkIdx(chunkIdxIn) {}
+ bool operator==(const Key &other) const {
+ return url == other.url && chunkIdx == other.chunkIdx;
+ }
+ };
+
+ struct KeyHasher {
+ std::size_t operator()(const Key &k) const {
+ return std::hash<std::string>{}(k.url) ^
+ (std::hash<unsigned long long>{}(k.chunkIdx) << 1);
+ }
+ };
+
+ lru11::Cache<
+ Key, std::shared_ptr<std::vector<unsigned char>>, MyMutex,
+ std::unordered_map<
+ Key,
+ typename std::list<lru11::KeyValuePair<
+ Key, std::shared_ptr<std::vector<unsigned char>>>>::iterator,
+ KeyHasher>>
+ cache_{MAX_CHUNKS};
+};
+
+// ---------------------------------------------------------------------------
+
+static NetworkChunkCache gNetworkChunkCache{};
+
+// ---------------------------------------------------------------------------
+
+class NetworkFilePropertiesCache {
+ public:
+ void insert(PJ_CONTEXT *ctx, const std::string &url, FileProperties &props);
+
+ bool tryGet(PJ_CONTEXT *ctx, const std::string &url, FileProperties &props);
+
+ void clearMemoryCache();
+
+ private:
+ lru11::Cache<std::string, FileProperties, MyMutex> cache_{};
+};
+
+// ---------------------------------------------------------------------------
+
+static NetworkFilePropertiesCache gNetworkFileProperties{};
+
+// ---------------------------------------------------------------------------
+
+class DiskChunkCache {
+ PJ_CONTEXT *ctx_ = nullptr;
+ std::string path_{};
+ sqlite3 *hDB_ = nullptr;
+ std::string thisNamePtr_{};
+ std::unique_ptr<SQLite3VFS> vfs_{};
+
+ explicit DiskChunkCache(PJ_CONTEXT *ctx, const std::string &path);
+
+ bool initialize();
+ void commitAndClose();
+
+ bool createDBStructure();
+ bool checkConsistency();
+ bool get_links(sqlite3_int64 chunk_id, sqlite3_int64 &link_id,
+ sqlite3_int64 &prev, sqlite3_int64 &next,
+ sqlite3_int64 &head, sqlite3_int64 &tail);
+ bool update_links_of_prev_and_next_links(sqlite3_int64 prev,
+ sqlite3_int64 next);
+ bool update_linked_chunks(sqlite3_int64 link_id, sqlite3_int64 prev,
+ sqlite3_int64 next);
+ bool update_linked_chunks_head_tail(sqlite3_int64 head, sqlite3_int64 tail);
+
+ DiskChunkCache(const DiskChunkCache &) = delete;
+ DiskChunkCache &operator=(const DiskChunkCache &) = delete;
+
+ public:
+ static std::unique_ptr<DiskChunkCache> open(PJ_CONTEXT *ctx);
+ ~DiskChunkCache();
+
+ sqlite3 *handle() { return hDB_; }
+ std::unique_ptr<SQLiteStatement> prepare(const char *sql);
+ bool move_to_head(sqlite3_int64 chunk_id);
+ bool move_to_tail(sqlite3_int64 chunk_id);
+ void closeAndUnlink();
+};
+
+// ---------------------------------------------------------------------------
+
+static bool pj_context_get_grid_cache_is_enabled(PJ_CONTEXT *ctx) {
+ pj_load_ini(ctx);
+ return ctx->gridChunkCache.enabled;
+}
+
+// ---------------------------------------------------------------------------
+
+static long long pj_context_get_grid_cache_max_size(PJ_CONTEXT *ctx) {
+ pj_load_ini(ctx);
+ return ctx->gridChunkCache.max_size;
+}
+
+// ---------------------------------------------------------------------------
+
+static int pj_context_get_grid_cache_ttl(PJ_CONTEXT *ctx) {
+ pj_load_ini(ctx);
+ return ctx->gridChunkCache.ttl;
+}
+
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<DiskChunkCache> DiskChunkCache::open(PJ_CONTEXT *ctx) {
+ if (!pj_context_get_grid_cache_is_enabled(ctx)) {
+ return nullptr;
+ }
+ const auto cachePath = pj_context_get_grid_cache_filename(ctx);
+ if (cachePath.empty()) {
+ return nullptr;
+ }
+
+ auto diskCache =
+ std::unique_ptr<DiskChunkCache>(new DiskChunkCache(ctx, cachePath));
+ if (!diskCache->initialize())
+ diskCache.reset();
+ return diskCache;
+}
+
+// ---------------------------------------------------------------------------
+
+DiskChunkCache::DiskChunkCache(PJ_CONTEXT *ctx, const std::string &path)
+ : ctx_(ctx), path_(path) {}
+
+// ---------------------------------------------------------------------------
+
+bool DiskChunkCache::initialize() {
+ std::string vfsName;
+ if (ctx_->custom_sqlite3_vfs_name.empty()) {
+ vfs_ = SQLite3VFS::create(true, false, false);
+ if (vfs_ == nullptr) {
+ return false;
+ }
+ vfsName = vfs_->name();
+ } else {
+ vfsName = ctx_->custom_sqlite3_vfs_name;
+ }
+ sqlite3_open_v2(path_.c_str(), &hDB_,
+ SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
+ vfsName.c_str());
+ if (!hDB_) {
+ pj_log(ctx_, PJ_LOG_ERROR, "Cannot open %s", path_.c_str());
+ return false;
+ }
+
+ // Cannot run more than 30 times / a bit more than one second.
+ for (int i = 0;; i++) {
+ int ret =
+ sqlite3_exec(hDB_, "BEGIN EXCLUSIVE", nullptr, nullptr, nullptr);
+ if (ret == SQLITE_OK) {
+ break;
+ }
+ if (ret != SQLITE_BUSY) {
+ pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
+ sqlite3_close(hDB_);
+ hDB_ = nullptr;
+ return false;
+ }
+ const char *max_iters = getenv("PROJ_LOCK_MAX_ITERS");
+ if (i >= (max_iters && max_iters[0] ? atoi(max_iters)
+ : 30)) { // A bit more than 1 second
+ pj_log(ctx_, PJ_LOG_ERROR, "Cannot take exclusive lock on %s",
+ path_.c_str());
+ sqlite3_close(hDB_);
+ hDB_ = nullptr;
+ return false;
+ }
+ pj_log(ctx_, PJ_LOG_TRACE, "Lock taken on cache. Waiting a bit...");
+ // Retry every 5 ms for 50 ms, then every 10 ms for 100 ms, then
+ // every 100 ms
+ sleep_ms(i < 10 ? 5 : i < 20 ? 10 : 100);
+ }
+ char **pasResult = nullptr;
+ int nRows = 0;
+ int nCols = 0;
+ sqlite3_get_table(hDB_,
+ "SELECT 1 FROM sqlite_master WHERE name = 'properties'",
+ &pasResult, &nRows, &nCols, nullptr);
+ sqlite3_free_table(pasResult);
+ if (nRows == 0) {
+ if (!createDBStructure()) {
+ sqlite3_close(hDB_);
+ hDB_ = nullptr;
+ return false;
+ }
+ }
+
+ if (getenv("PROJ_CHECK_CACHE_CONSISTENCY")) {
+ checkConsistency();
+ }
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+static const char *cache_db_structure_sql =
+ "CREATE TABLE properties("
+ " url TEXT PRIMARY KEY NOT NULL,"
+ " lastChecked TIMESTAMP NOT NULL,"
+ " fileSize INTEGER NOT NULL,"
+ " lastModified TEXT,"
+ " etag TEXT"
+ ");"
+ "CREATE TABLE downloaded_file_properties("
+ " url TEXT PRIMARY KEY NOT NULL,"
+ " lastChecked TIMESTAMP NOT NULL,"
+ " fileSize INTEGER NOT NULL,"
+ " lastModified TEXT,"
+ " etag TEXT"
+ ");"
+ "CREATE TABLE chunk_data("
+ " id INTEGER PRIMARY KEY AUTOINCREMENT CHECK (id > 0),"
+ " data BLOB NOT NULL"
+ ");"
+ "CREATE TABLE chunks("
+ " id INTEGER PRIMARY KEY AUTOINCREMENT CHECK (id > 0),"
+ " url TEXT NOT NULL,"
+ " offset INTEGER NOT NULL,"
+ " data_id INTEGER NOT NULL,"
+ " data_size INTEGER NOT NULL,"
+ " CONSTRAINT fk_chunks_url FOREIGN KEY (url) REFERENCES properties(url),"
+ " CONSTRAINT fk_chunks_data FOREIGN KEY (data_id) REFERENCES chunk_data(id)"
+ ");"
+ "CREATE INDEX idx_chunks ON chunks(url, offset);"
+ "CREATE TABLE linked_chunks("
+ " id INTEGER PRIMARY KEY AUTOINCREMENT CHECK (id > 0),"
+ " chunk_id INTEGER NOT NULL,"
+ " prev INTEGER,"
+ " next INTEGER,"
+ " CONSTRAINT fk_links_chunkid FOREIGN KEY (chunk_id) REFERENCES chunks(id),"
+ " CONSTRAINT fk_links_prev FOREIGN KEY (prev) REFERENCES linked_chunks(id),"
+ " CONSTRAINT fk_links_next FOREIGN KEY (next) REFERENCES linked_chunks(id)"
+ ");"
+ "CREATE INDEX idx_linked_chunks_chunk_id ON linked_chunks(chunk_id);"
+ "CREATE TABLE linked_chunks_head_tail("
+ " head INTEGER,"
+ " tail INTEGER,"
+ " CONSTRAINT lht_head FOREIGN KEY (head) REFERENCES linked_chunks(id),"
+ " CONSTRAINT lht_tail FOREIGN KEY (tail) REFERENCES linked_chunks(id)"
+ ");"
+ "INSERT INTO linked_chunks_head_tail VALUES (NULL, NULL);";
+
+bool DiskChunkCache::createDBStructure() {
+
+ pj_log(ctx_, PJ_LOG_TRACE, "Creating cache DB structure");
+ if (sqlite3_exec(hDB_, cache_db_structure_sql, nullptr, nullptr, nullptr) !=
+ SQLITE_OK) {
+ pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
+ return false;
+ }
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+// Used by checkConsistency() and insert()
+#define INVALIDATED_SQL_LITERAL "'invalidated'"
+
+bool DiskChunkCache::checkConsistency() {
+
+ auto stmt = prepare("SELECT * FROM chunk_data WHERE id NOT IN (SELECT "
+ "data_id FROM chunks)");
+ if (!stmt) {
+ return false;
+ }
+ if (stmt->execute() != SQLITE_DONE) {
+ fprintf(stderr, "Rows in chunk_data not referenced by chunks.\n");
+ return false;
+ }
+
+ stmt = prepare("SELECT * FROM chunks WHERE id NOT IN (SELECT chunk_id FROM "
+ "linked_chunks)");
+ if (!stmt) {
+ return false;
+ }
+ if (stmt->execute() != SQLITE_DONE) {
+ fprintf(stderr, "Rows in chunks not referenced by linked_chunks.\n");
+ return false;
+ }
+
+ stmt = prepare("SELECT * FROM chunks WHERE url <> " INVALIDATED_SQL_LITERAL
+ " AND url "
+ "NOT IN (SELECT url FROM properties)");
+ if (!stmt) {
+ return false;
+ }
+ if (stmt->execute() != SQLITE_DONE) {
+ fprintf(stderr, "url values in chunks not referenced by properties.\n");
+ return false;
+ }
+
+ stmt = prepare("SELECT head, tail FROM linked_chunks_head_tail");
+ if (!stmt) {
+ return false;
+ }
+ if (stmt->execute() != SQLITE_ROW) {
+ fprintf(stderr, "linked_chunks_head_tail empty.\n");
+ return false;
+ }
+ const auto head = stmt->getInt64();
+ const auto tail = stmt->getInt64();
+ if (stmt->execute() != SQLITE_DONE) {
+ fprintf(stderr, "linked_chunks_head_tail has more than one row.\n");
+ return false;
+ }
+
+ stmt = prepare("SELECT COUNT(*) FROM linked_chunks");
+ if (!stmt) {
+ return false;
+ }
+ if (stmt->execute() != SQLITE_ROW) {
+ fprintf(stderr, "linked_chunks_head_tail empty.\n");
+ return false;
+ }
+ const auto count_linked_chunks = stmt->getInt64();
+
+ if (head) {
+ auto id = head;
+ std::set<sqlite3_int64> visitedIds;
+ stmt = prepare("SELECT next FROM linked_chunks WHERE id = ?");
+ if (!stmt) {
+ return false;
+ }
+ while (true) {
+ visitedIds.insert(id);
+ stmt->reset();
+ stmt->bindInt64(id);
+ if (stmt->execute() != SQLITE_ROW) {
+ fprintf(stderr, "cannot find linked_chunks.id = %d.\n",
+ static_cast<int>(id));
+ return false;
+ }
+ auto next = stmt->getInt64();
+ if (next == 0) {
+ if (id != tail) {
+ fprintf(stderr,
+ "last item when following next is not tail.\n");
+ return false;
+ }
+ break;
+ }
+ if (visitedIds.find(next) != visitedIds.end()) {
+ fprintf(stderr, "found cycle on linked_chunks.next = %d.\n",
+ static_cast<int>(next));
+ return false;
+ }
+ id = next;
+ }
+ if (visitedIds.size() != static_cast<size_t>(count_linked_chunks)) {
+ fprintf(stderr,
+ "ghost items in linked_chunks when following next.\n");
+ return false;
+ }
+ } else if (count_linked_chunks) {
+ fprintf(stderr, "linked_chunks_head_tail.head = NULL but linked_chunks "
+ "not empty.\n");
+ return false;
+ }
+
+ if (tail) {
+ auto id = tail;
+ std::set<sqlite3_int64> visitedIds;
+ stmt = prepare("SELECT prev FROM linked_chunks WHERE id = ?");
+ if (!stmt) {
+ return false;
+ }
+ while (true) {
+ visitedIds.insert(id);
+ stmt->reset();
+ stmt->bindInt64(id);
+ if (stmt->execute() != SQLITE_ROW) {
+ fprintf(stderr, "cannot find linked_chunks.id = %d.\n",
+ static_cast<int>(id));
+ return false;
+ }
+ auto prev = stmt->getInt64();
+ if (prev == 0) {
+ if (id != head) {
+ fprintf(stderr,
+ "last item when following prev is not head.\n");
+ return false;
+ }
+ break;
+ }
+ if (visitedIds.find(prev) != visitedIds.end()) {
+ fprintf(stderr, "found cycle on linked_chunks.prev = %d.\n",
+ static_cast<int>(prev));
+ return false;
+ }
+ id = prev;
+ }
+ if (visitedIds.size() != static_cast<size_t>(count_linked_chunks)) {
+ fprintf(stderr,
+ "ghost items in linked_chunks when following prev.\n");
+ return false;
+ }
+ } else if (count_linked_chunks) {
+ fprintf(stderr, "linked_chunks_head_tail.tail = NULL but linked_chunks "
+ "not empty.\n");
+ return false;
+ }
+
+ fprintf(stderr, "check ok\n");
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+void DiskChunkCache::commitAndClose() {
+ if (hDB_) {
+ if( sqlite3_exec(hDB_, "COMMIT", nullptr, nullptr, nullptr) != SQLITE_OK ) {
+ pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
+ }
+ sqlite3_close(hDB_);
+ hDB_ = nullptr;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+DiskChunkCache::~DiskChunkCache() {
+ commitAndClose();
+}
+
+// ---------------------------------------------------------------------------
+
+void DiskChunkCache::closeAndUnlink() {
+ commitAndClose();
+ if (vfs_) {
+ vfs_->raw()->xDelete(vfs_->raw(), path_.c_str(), 0);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<SQLiteStatement> DiskChunkCache::prepare(const char *sql) {
+ sqlite3_stmt *hStmt = nullptr;
+ sqlite3_prepare_v2(hDB_, sql, -1, &hStmt, nullptr);
+ if (!hStmt) {
+ pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
+ return nullptr;
+ }
+ return std::unique_ptr<SQLiteStatement>(new SQLiteStatement(hStmt));
+}
+
+// ---------------------------------------------------------------------------
+
+bool DiskChunkCache::get_links(sqlite3_int64 chunk_id, sqlite3_int64 &link_id,
+ sqlite3_int64 &prev, sqlite3_int64 &next,
+ sqlite3_int64 &head, sqlite3_int64 &tail) {
+ auto stmt =
+ prepare("SELECT id, prev, next FROM linked_chunks WHERE chunk_id = ?");
+ if (!stmt)
+ return false;
+ stmt->bindInt64(chunk_id);
+ {
+ const auto ret = stmt->execute();
+ if (ret != SQLITE_ROW) {
+ pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
+ return false;
+ }
+ }
+ link_id = stmt->getInt64();
+ prev = stmt->getInt64();
+ next = stmt->getInt64();
+
+ stmt = prepare("SELECT head, tail FROM linked_chunks_head_tail");
+ {
+ const auto ret = stmt->execute();
+ if (ret != SQLITE_ROW) {
+ pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
+ return false;
+ }
+ }
+ head = stmt->getInt64();
+ tail = stmt->getInt64();
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+bool DiskChunkCache::update_links_of_prev_and_next_links(sqlite3_int64 prev,
+ sqlite3_int64 next) {
+ if (prev) {
+ auto stmt = prepare("UPDATE linked_chunks SET next = ? WHERE id = ?");
+ if (!stmt)
+ return false;
+ if (next)
+ stmt->bindInt64(next);
+ else
+ stmt->bindNull();
+ stmt->bindInt64(prev);
+ const auto ret = stmt->execute();
+ if (ret != SQLITE_DONE) {
+ pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
+ return false;
+ }
+ }
+
+ if (next) {
+ auto stmt = prepare("UPDATE linked_chunks SET prev = ? WHERE id = ?");
+ if (!stmt)
+ return false;
+ if (prev)
+ stmt->bindInt64(prev);
+ else
+ stmt->bindNull();
+ stmt->bindInt64(next);
+ const auto ret = stmt->execute();
+ if (ret != SQLITE_DONE) {
+ pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
+ return false;
+ }
+ }
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+bool DiskChunkCache::update_linked_chunks(sqlite3_int64 link_id,
+ sqlite3_int64 prev,
+ sqlite3_int64 next) {
+ auto stmt =
+ prepare("UPDATE linked_chunks SET prev = ?, next = ? WHERE id = ?");
+ if (!stmt)
+ return false;
+ if (prev)
+ stmt->bindInt64(prev);
+ else
+ stmt->bindNull();
+ if (next)
+ stmt->bindInt64(next);
+ else
+ stmt->bindNull();
+ stmt->bindInt64(link_id);
+ const auto ret = stmt->execute();
+ if (ret != SQLITE_DONE) {
+ pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
+ return false;
+ }
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+bool DiskChunkCache::update_linked_chunks_head_tail(sqlite3_int64 head,
+ sqlite3_int64 tail) {
+ auto stmt =
+ prepare("UPDATE linked_chunks_head_tail SET head = ?, tail = ?");
+ if (!stmt)
+ return false;
+ if (head)
+ stmt->bindInt64(head);
+ else
+ stmt->bindNull(); // shouldn't happen normally
+ if (tail)
+ stmt->bindInt64(tail);
+ else
+ stmt->bindNull(); // shouldn't happen normally
+ const auto ret = stmt->execute();
+ if (ret != SQLITE_DONE) {
+ pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
+ return false;
+ }
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+bool DiskChunkCache::move_to_head(sqlite3_int64 chunk_id) {
+
+ sqlite3_int64 link_id = 0;
+ sqlite3_int64 prev = 0;
+ sqlite3_int64 next = 0;
+ sqlite3_int64 head = 0;
+ sqlite3_int64 tail = 0;
+ if (!get_links(chunk_id, link_id, prev, next, head, tail)) {
+ return false;
+ }
+
+ if (link_id == head) {
+ return true;
+ }
+
+ if (!update_links_of_prev_and_next_links(prev, next)) {
+ return false;
+ }
+
+ if (head) {
+ auto stmt = prepare("UPDATE linked_chunks SET prev = ? WHERE id = ?");
+ if (!stmt)
+ return false;
+ stmt->bindInt64(link_id);
+ stmt->bindInt64(head);
+ const auto ret = stmt->execute();
+ if (ret != SQLITE_DONE) {
+ pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
+ return false;
+ }
+ }
+
+ return update_linked_chunks(link_id, 0, head) &&
+ update_linked_chunks_head_tail(link_id,
+ (link_id == tail) ? prev : tail);
+}
+
+// ---------------------------------------------------------------------------
+
+bool DiskChunkCache::move_to_tail(sqlite3_int64 chunk_id) {
+ sqlite3_int64 link_id = 0;
+ sqlite3_int64 prev = 0;
+ sqlite3_int64 next = 0;
+ sqlite3_int64 head = 0;
+ sqlite3_int64 tail = 0;
+ if (!get_links(chunk_id, link_id, prev, next, head, tail)) {
+ return false;
+ }
+
+ if (link_id == tail) {
+ return true;
+ }
+
+ if (!update_links_of_prev_and_next_links(prev, next)) {
+ return false;
+ }
+
+ if (tail) {
+ auto stmt = prepare("UPDATE linked_chunks SET next = ? WHERE id = ?");
+ if (!stmt)
+ return false;
+ stmt->bindInt64(link_id);
+ stmt->bindInt64(tail);
+ const auto ret = stmt->execute();
+ if (ret != SQLITE_DONE) {
+ pj_log(ctx_, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB_));
+ return false;
+ }
+ }
+
+ return update_linked_chunks(link_id, tail, 0) &&
+ update_linked_chunks_head_tail((link_id == head) ? next : head,
+ link_id);
+}
+
+// ---------------------------------------------------------------------------
+
+void NetworkChunkCache::insert(PJ_CONTEXT *ctx, const std::string &url,
+ unsigned long long chunkIdx,
+ std::vector<unsigned char> &&data) {
+ auto dataPtr(std::make_shared<std::vector<unsigned char>>(std::move(data)));
+ cache_.insert(Key(url, chunkIdx), dataPtr);
+
+ auto diskCache = DiskChunkCache::open(ctx);
+ if (!diskCache)
+ return;
+ auto hDB = diskCache->handle();
+
+ // Always insert DOWNLOAD_CHUNK_SIZE bytes to avoid fragmentation
+ std::vector<unsigned char> blob(*dataPtr);
+ assert(blob.size() <= DOWNLOAD_CHUNK_SIZE);
+ blob.resize(DOWNLOAD_CHUNK_SIZE);
+
+ // Check if there is an existing entry for that URL and offset
+ auto stmt = diskCache->prepare(
+ "SELECT id, data_id FROM chunks WHERE url = ? AND offset = ?");
+ if (!stmt)
+ return;
+ stmt->bindText(url.c_str());
+ stmt->bindInt64(chunkIdx * DOWNLOAD_CHUNK_SIZE);
+
+ const auto mainRet = stmt->execute();
+ if (mainRet == SQLITE_ROW) {
+ const auto chunk_id = stmt->getInt64();
+ const auto data_id = stmt->getInt64();
+ stmt =
+ diskCache->prepare("UPDATE chunk_data SET data = ? WHERE id = ?");
+ if (!stmt)
+ return;
+ stmt->bindBlob(blob.data(), blob.size());
+ stmt->bindInt64(data_id);
+ {
+ const auto ret = stmt->execute();
+ if (ret != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+ }
+
+ diskCache->move_to_head(chunk_id);
+
+ return;
+ } else if (mainRet != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+
+ // Lambda to recycle an existing entry that was either invalidated, or
+ // least recently used.
+ const auto reuseExistingEntry = [ctx, &blob, &diskCache, hDB, &url,
+ chunkIdx, &dataPtr](
+ std::unique_ptr<SQLiteStatement> &stmtIn) {
+ const auto chunk_id = stmtIn->getInt64();
+ const auto data_id = stmtIn->getInt64();
+ if (data_id <= 0) {
+ pj_log(ctx, PJ_LOG_ERROR, "data_id <= 0");
+ return;
+ }
+
+ auto l_stmt =
+ diskCache->prepare("UPDATE chunk_data SET data = ? WHERE id = ?");
+ if (!l_stmt)
+ return;
+ l_stmt->bindBlob(blob.data(), blob.size());
+ l_stmt->bindInt64(data_id);
+ {
+ const auto ret2 = l_stmt->execute();
+ if (ret2 != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+ }
+
+ l_stmt = diskCache->prepare("UPDATE chunks SET url = ?, "
+ "offset = ?, data_size = ?, data_id = ? "
+ "WHERE id = ?");
+ if (!l_stmt)
+ return;
+ l_stmt->bindText(url.c_str());
+ l_stmt->bindInt64(chunkIdx * DOWNLOAD_CHUNK_SIZE);
+ l_stmt->bindInt64(dataPtr->size());
+ l_stmt->bindInt64(data_id);
+ l_stmt->bindInt64(chunk_id);
+ {
+ const auto ret2 = l_stmt->execute();
+ if (ret2 != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+ }
+
+ diskCache->move_to_head(chunk_id);
+ };
+
+ // Find if there is an invalidated chunk we can reuse
+ stmt = diskCache->prepare(
+ "SELECT id, data_id FROM chunks "
+ "WHERE id = (SELECT tail FROM linked_chunks_head_tail) AND "
+ "url = " INVALIDATED_SQL_LITERAL);
+ if (!stmt)
+ return;
+ {
+ const auto ret = stmt->execute();
+ if (ret == SQLITE_ROW) {
+ reuseExistingEntry(stmt);
+ return;
+ } else if (ret != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+ }
+
+ // Check if we have not reached the max size of the cache
+ stmt = diskCache->prepare("SELECT COUNT(*) FROM chunks");
+ if (!stmt)
+ return;
+ {
+ const auto ret = stmt->execute();
+ if (ret != SQLITE_ROW) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+ }
+
+ const auto max_size = pj_context_get_grid_cache_max_size(ctx);
+ if (max_size > 0 &&
+ static_cast<long long>(stmt->getInt64() * DOWNLOAD_CHUNK_SIZE) >=
+ max_size) {
+ stmt = diskCache->prepare(
+ "SELECT id, data_id FROM chunks "
+ "WHERE id = (SELECT tail FROM linked_chunks_head_tail)");
+ if (!stmt)
+ return;
+
+ const auto ret = stmt->execute();
+ if (ret != SQLITE_ROW) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+ reuseExistingEntry(stmt);
+ return;
+ }
+
+ // Otherwise just append a new entry
+ stmt = diskCache->prepare("INSERT INTO chunk_data(data) VALUES (?)");
+ if (!stmt)
+ return;
+ stmt->bindBlob(blob.data(), blob.size());
+ {
+ const auto ret = stmt->execute();
+ if (ret != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+ }
+
+ const auto chunk_data_id = sqlite3_last_insert_rowid(hDB);
+
+ stmt = diskCache->prepare("INSERT INTO chunks(url, offset, data_id, "
+ "data_size) VALUES (?,?,?,?)");
+ if (!stmt)
+ return;
+ stmt->bindText(url.c_str());
+ stmt->bindInt64(chunkIdx * DOWNLOAD_CHUNK_SIZE);
+ stmt->bindInt64(chunk_data_id);
+ stmt->bindInt64(dataPtr->size());
+ {
+ const auto ret = stmt->execute();
+ if (ret != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+ }
+
+ const auto chunk_id = sqlite3_last_insert_rowid(hDB);
+
+ stmt = diskCache->prepare(
+ "INSERT INTO linked_chunks(chunk_id, prev, next) VALUES (?,NULL,NULL)");
+ if (!stmt)
+ return;
+ stmt->bindInt64(chunk_id);
+ if (stmt->execute() != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+
+ stmt = diskCache->prepare("SELECT head FROM linked_chunks_head_tail");
+ if (!stmt)
+ return;
+ if (stmt->execute() != SQLITE_ROW) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+ if (stmt->getInt64() == 0) {
+ stmt = diskCache->prepare(
+ "UPDATE linked_chunks_head_tail SET head = ?, tail = ?");
+ if (!stmt)
+ return;
+ stmt->bindInt64(chunk_id);
+ stmt->bindInt64(chunk_id);
+ if (stmt->execute() != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+ }
+
+ diskCache->move_to_head(chunk_id);
+}
+
+// ---------------------------------------------------------------------------
+
+std::shared_ptr<std::vector<unsigned char>>
+NetworkChunkCache::get(PJ_CONTEXT *ctx, const std::string &url,
+ unsigned long long chunkIdx) {
+ std::shared_ptr<std::vector<unsigned char>> ret;
+ if (cache_.tryGet(Key(url, chunkIdx), ret)) {
+ return ret;
+ }
+
+ auto diskCache = DiskChunkCache::open(ctx);
+ if (!diskCache)
+ return ret;
+ auto hDB = diskCache->handle();
+
+ auto stmt = diskCache->prepare(
+ "SELECT chunks.id, chunks.data_size, chunk_data.data FROM chunks "
+ "JOIN chunk_data ON chunks.id = chunk_data.id "
+ "WHERE chunks.url = ? AND chunks.offset = ?");
+ if (!stmt)
+ return ret;
+
+ stmt->bindText(url.c_str());
+ stmt->bindInt64(chunkIdx * DOWNLOAD_CHUNK_SIZE);
+
+ const auto mainRet = stmt->execute();
+ if (mainRet == SQLITE_ROW) {
+ const auto chunk_id = stmt->getInt64();
+ const auto data_size = stmt->getInt64();
+ int blob_size = 0;
+ const void *blob = stmt->getBlob(blob_size);
+ if (blob_size < data_size) {
+ pj_log(ctx, PJ_LOG_ERROR,
+ "blob_size=%d < data_size for chunk_id=%d", blob_size,
+ static_cast<int>(chunk_id));
+ return ret;
+ }
+ if (data_size > static_cast<sqlite3_int64>(DOWNLOAD_CHUNK_SIZE)) {
+ pj_log(ctx, PJ_LOG_ERROR, "data_size > DOWNLOAD_CHUNK_SIZE");
+ return ret;
+ }
+ ret.reset(new std::vector<unsigned char>());
+ ret->assign(reinterpret_cast<const unsigned char *>(blob),
+ reinterpret_cast<const unsigned char *>(blob) +
+ static_cast<size_t>(data_size));
+ cache_.insert(Key(url, chunkIdx), ret);
+
+ if (!diskCache->move_to_head(chunk_id))
+ return ret;
+ } else if (mainRet != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ }
+
+ return ret;
+}
+
+// ---------------------------------------------------------------------------
+
+std::shared_ptr<std::vector<unsigned char>>
+NetworkChunkCache::get(PJ_CONTEXT *ctx, const std::string &url,
+ unsigned long long chunkIdx, FileProperties &props) {
+ if (!gNetworkFileProperties.tryGet(ctx, url, props)) {
+ return nullptr;
+ }
+
+ return get(ctx, url, chunkIdx);
+}
+
+// ---------------------------------------------------------------------------
+
+void NetworkChunkCache::clearMemoryCache() { cache_.clear(); }
+
+// ---------------------------------------------------------------------------
+
+void NetworkChunkCache::clearDiskChunkCache(PJ_CONTEXT *ctx) {
+ auto diskCache = DiskChunkCache::open(ctx);
+ if (!diskCache)
+ return;
+ diskCache->closeAndUnlink();
+}
+
+// ---------------------------------------------------------------------------
+
+void NetworkFilePropertiesCache::insert(PJ_CONTEXT *ctx, const std::string &url,
+ FileProperties &props) {
+ time(&props.lastChecked);
+ cache_.insert(url, props);
+
+ auto diskCache = DiskChunkCache::open(ctx);
+ if (!diskCache)
+ return;
+ auto hDB = diskCache->handle();
+ auto stmt = diskCache->prepare("SELECT fileSize, lastModified, etag "
+ "FROM properties WHERE url = ?");
+ if (!stmt)
+ return;
+ stmt->bindText(url.c_str());
+ if (stmt->execute() == SQLITE_ROW) {
+ FileProperties cachedProps;
+ cachedProps.size = stmt->getInt64();
+ const char *lastModified = stmt->getText();
+ cachedProps.lastModified = lastModified ? lastModified : std::string();
+ const char *etag = stmt->getText();
+ cachedProps.etag = etag ? etag : std::string();
+ if (props.size != cachedProps.size ||
+ props.lastModified != cachedProps.lastModified ||
+ props.etag != cachedProps.etag) {
+
+ // If cached properties don't match recent fresh ones, invalidate
+ // cached chunks
+ stmt = diskCache->prepare("SELECT id FROM chunks WHERE url = ?");
+ if (!stmt)
+ return;
+ stmt->bindText(url.c_str());
+ std::vector<sqlite3_int64> ids;
+ while (stmt->execute() == SQLITE_ROW) {
+ ids.emplace_back(stmt->getInt64());
+ stmt->resetResIndex();
+ }
+
+ for (const auto id : ids) {
+ diskCache->move_to_tail(id);
+ }
+
+ stmt = diskCache->prepare(
+ "UPDATE chunks SET url = " INVALIDATED_SQL_LITERAL ", "
+ "offset = -1, data_size = 0 WHERE url = ?");
+ if (!stmt)
+ return;
+ stmt->bindText(url.c_str());
+ if (stmt->execute() != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+ }
+
+ stmt = diskCache->prepare("UPDATE properties SET lastChecked = ?, "
+ "fileSize = ?, lastModified = ?, etag = ? "
+ "WHERE url = ?");
+ if (!stmt)
+ return;
+ stmt->bindInt64(props.lastChecked);
+ stmt->bindInt64(props.size);
+ if (props.lastModified.empty())
+ stmt->bindNull();
+ else
+ stmt->bindText(props.lastModified.c_str());
+ if (props.etag.empty())
+ stmt->bindNull();
+ else
+ stmt->bindText(props.etag.c_str());
+ stmt->bindText(url.c_str());
+ if (stmt->execute() != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+ } else {
+ stmt = diskCache->prepare("INSERT INTO properties (url, lastChecked, "
+ "fileSize, lastModified, etag) VALUES "
+ "(?,?,?,?,?)");
+ if (!stmt)
+ return;
+ stmt->bindText(url.c_str());
+ stmt->bindInt64(props.lastChecked);
+ stmt->bindInt64(props.size);
+ if (props.lastModified.empty())
+ stmt->bindNull();
+ else
+ stmt->bindText(props.lastModified.c_str());
+ if (props.etag.empty())
+ stmt->bindNull();
+ else
+ stmt->bindText(props.etag.c_str());
+ if (stmt->execute() != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return;
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+bool NetworkFilePropertiesCache::tryGet(PJ_CONTEXT *ctx, const std::string &url,
+ FileProperties &props) {
+ if (cache_.tryGet(url, props)) {
+ return true;
+ }
+
+ auto diskCache = DiskChunkCache::open(ctx);
+ if (!diskCache)
+ return false;
+ auto stmt =
+ diskCache->prepare("SELECT lastChecked, fileSize, lastModified, etag "
+ "FROM properties WHERE url = ?");
+ if (!stmt)
+ return false;
+ stmt->bindText(url.c_str());
+ if (stmt->execute() != SQLITE_ROW) {
+ return false;
+ }
+ props.lastChecked = stmt->getInt64();
+ props.size = stmt->getInt64();
+ const char *lastModified = stmt->getText();
+ props.lastModified = lastModified ? lastModified : std::string();
+ const char *etag = stmt->getText();
+ props.etag = etag ? etag : std::string();
+
+ const auto ttl = pj_context_get_grid_cache_ttl(ctx);
+ if (ttl > 0) {
+ time_t curTime;
+ time(&curTime);
+ if (curTime > props.lastChecked + ttl) {
+ props = FileProperties();
+ return false;
+ }
+ }
+ cache_.insert(url, props);
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+void NetworkFilePropertiesCache::clearMemoryCache() { cache_.clear(); }
+
+// ---------------------------------------------------------------------------
+
+class NetworkFile : public File {
+ PJ_CONTEXT *m_ctx;
+ std::string m_url;
+ PROJ_NETWORK_HANDLE *m_handle;
+ unsigned long long m_pos = 0;
+ size_t m_nBlocksToDownload = 1;
+ unsigned long long m_lastDownloadedOffset;
+ FileProperties m_props;
+ proj_network_close_cbk_type m_closeCbk;
+ bool m_hasChanged = false;
+
+ NetworkFile(const NetworkFile &) = delete;
+ NetworkFile &operator=(const NetworkFile &) = delete;
+
+ protected:
+ NetworkFile(PJ_CONTEXT *ctx, const std::string &url,
+ PROJ_NETWORK_HANDLE *handle,
+ unsigned long long lastDownloadOffset,
+ const FileProperties &props)
+ : File(url), m_ctx(ctx), m_url(url), m_handle(handle),
+ m_lastDownloadedOffset(lastDownloadOffset), m_props(props),
+ m_closeCbk(ctx->networking.close) {}
+
+ public:
+ ~NetworkFile() override;
+
+ size_t read(void *buffer, size_t sizeBytes) override;
+ size_t write(const void *, size_t) override { return 0; }
+ bool seek(unsigned long long offset, int whence) override;
+ unsigned long long tell() override;
+ void reassign_context(PJ_CONTEXT *ctx) override;
+ bool hasChanged() const override { return m_hasChanged; }
+
+ static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename);
+
+ static bool get_props_from_headers(PJ_CONTEXT *ctx,
+ PROJ_NETWORK_HANDLE *handle,
+ FileProperties &props);
+};
+
+// ---------------------------------------------------------------------------
+
+bool NetworkFile::get_props_from_headers(PJ_CONTEXT *ctx,
+ PROJ_NETWORK_HANDLE *handle,
+ FileProperties &props) {
+ const char *contentRange = ctx->networking.get_header_value(
+ ctx, handle, "Content-Range", ctx->networking.user_data);
+ if (contentRange) {
+ const char *slash = strchr(contentRange, '/');
+ if (slash) {
+ props.size = std::stoull(slash + 1);
+
+ const char *lastModified = ctx->networking.get_header_value(
+ ctx, handle, "Last-Modified", ctx->networking.user_data);
+ if (lastModified)
+ props.lastModified = lastModified;
+
+ const char *etag = ctx->networking.get_header_value(
+ ctx, handle, "ETag", ctx->networking.user_data);
+ if (etag)
+ props.etag = etag;
+
+ return true;
+ }
+ }
+ return false;
+}
+
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<File> NetworkFile::open(PJ_CONTEXT *ctx, const char *filename) {
+ FileProperties props;
+ if (gNetworkChunkCache.get(ctx, filename, 0, props)) {
+ return std::unique_ptr<File>(new NetworkFile(
+ ctx, filename, nullptr,
+ std::numeric_limits<unsigned long long>::max(), props));
+ } else {
+ std::vector<unsigned char> buffer(DOWNLOAD_CHUNK_SIZE);
+ size_t size_read = 0;
+ std::string errorBuffer;
+ errorBuffer.resize(1024);
+
+ auto handle = ctx->networking.open(
+ ctx, filename, 0, buffer.size(), &buffer[0], &size_read,
+ errorBuffer.size(), &errorBuffer[0], ctx->networking.user_data);
+ buffer.resize(size_read);
+ if (!handle) {
+ errorBuffer.resize(strlen(errorBuffer.data()));
+ pj_log(ctx, PJ_LOG_ERROR, "Cannot open %s: %s", filename,
+ errorBuffer.c_str());
+ pj_ctx_set_errno(ctx, PJD_ERR_NETWORK_ERROR);
+ }
+
+ bool ok = false;
+ if (handle) {
+ if (get_props_from_headers(ctx, handle, props)) {
+ ok = true;
+ gNetworkFileProperties.insert(ctx, filename, props);
+ gNetworkChunkCache.insert(ctx, filename, 0, std::move(buffer));
+ }
+ }
+
+ return std::unique_ptr<File>(
+ ok ? new NetworkFile(ctx, filename, handle, size_read, props)
+ : nullptr);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<File> pj_network_file_open(PJ_CONTEXT* ctx, const char* filename) {
+ return NetworkFile::open(ctx, filename);
+}
+
+// ---------------------------------------------------------------------------
+
+size_t NetworkFile::read(void *buffer, size_t sizeBytes) {
+
+ if (sizeBytes == 0)
+ return 0;
+
+ auto iterOffset = m_pos;
+ while (sizeBytes) {
+ const auto chunkIdxToDownload = iterOffset / DOWNLOAD_CHUNK_SIZE;
+ const auto offsetToDownload = chunkIdxToDownload * DOWNLOAD_CHUNK_SIZE;
+ std::vector<unsigned char> region;
+ auto pChunk = gNetworkChunkCache.get(m_ctx, m_url, chunkIdxToDownload);
+ if (pChunk != nullptr) {
+ region = *pChunk;
+ } else {
+ if (offsetToDownload == m_lastDownloadedOffset) {
+ // In case of consecutive reads (of small size), we use a
+ // heuristic that we will read the file sequentially, so
+ // we double the requested size to decrease the number of
+ // client/server roundtrips.
+ if (m_nBlocksToDownload < 100)
+ m_nBlocksToDownload *= 2;
+ } else {
+ // Random reads. Cancel the above heuristics.
+ m_nBlocksToDownload = 1;
+ }
+
+ // Ensure that we will request at least the number of blocks
+ // to satisfy the remaining buffer size to read.
+ const auto endOffsetToDownload =
+ ((iterOffset + sizeBytes + DOWNLOAD_CHUNK_SIZE - 1) /
+ DOWNLOAD_CHUNK_SIZE) *
+ DOWNLOAD_CHUNK_SIZE;
+ const auto nMinBlocksToDownload = static_cast<size_t>(
+ (endOffsetToDownload - offsetToDownload) / DOWNLOAD_CHUNK_SIZE);
+ if (m_nBlocksToDownload < nMinBlocksToDownload)
+ m_nBlocksToDownload = nMinBlocksToDownload;
+
+ // Avoid reading already cached data.
+ // Note: this might get evicted if concurrent reads are done, but
+ // this should not cause bugs. Just missed optimization.
+ for (size_t i = 1; i < m_nBlocksToDownload; i++) {
+ if (gNetworkChunkCache.get(m_ctx, m_url,
+ chunkIdxToDownload + i) != nullptr) {
+ m_nBlocksToDownload = i;
+ break;
+ }
+ }
+
+ if (m_nBlocksToDownload > MAX_CHUNKS)
+ m_nBlocksToDownload = MAX_CHUNKS;
+
+ region.resize(m_nBlocksToDownload * DOWNLOAD_CHUNK_SIZE);
+ size_t nRead = 0;
+ std::string errorBuffer;
+ errorBuffer.resize(1024);
+ if (!m_handle) {
+ m_handle = m_ctx->networking.open(
+ m_ctx, m_url.c_str(), offsetToDownload,
+ m_nBlocksToDownload * DOWNLOAD_CHUNK_SIZE, &region[0],
+ &nRead, errorBuffer.size(), &errorBuffer[0],
+ m_ctx->networking.user_data);
+ if (!m_handle) {
+ pj_ctx_set_errno(m_ctx, PJD_ERR_NETWORK_ERROR);
+ return 0;
+ }
+ } else {
+ nRead = m_ctx->networking.read_range(
+ m_ctx, m_handle, offsetToDownload,
+ m_nBlocksToDownload * DOWNLOAD_CHUNK_SIZE, &region[0],
+ errorBuffer.size(), &errorBuffer[0],
+ m_ctx->networking.user_data);
+ }
+ if (nRead == 0) {
+ errorBuffer.resize(strlen(errorBuffer.data()));
+ if (!errorBuffer.empty()) {
+ pj_log(m_ctx, PJ_LOG_ERROR, "Cannot read in %s: %s",
+ m_url.c_str(), errorBuffer.c_str());
+ }
+ pj_ctx_set_errno(m_ctx, PJD_ERR_NETWORK_ERROR);
+ return 0;
+ }
+
+ if (!m_hasChanged) {
+ FileProperties props;
+ if (get_props_from_headers(m_ctx, m_handle, props)) {
+ if (props.size != m_props.size ||
+ props.lastModified != m_props.lastModified ||
+ props.etag != m_props.etag) {
+ gNetworkFileProperties.insert(m_ctx, m_url, props);
+ gNetworkChunkCache.clearMemoryCache();
+ m_hasChanged = true;
+ }
+ }
+ }
+
+ region.resize(nRead);
+ m_lastDownloadedOffset = offsetToDownload + nRead;
+
+ const auto nChunks =
+ (region.size() + DOWNLOAD_CHUNK_SIZE - 1) / DOWNLOAD_CHUNK_SIZE;
+ for (size_t i = 0; i < nChunks; i++) {
+ std::vector<unsigned char> chunk(
+ region.data() + i * DOWNLOAD_CHUNK_SIZE,
+ region.data() +
+ std::min((i + 1) * DOWNLOAD_CHUNK_SIZE, region.size()));
+ gNetworkChunkCache.insert(m_ctx, m_url, chunkIdxToDownload + i,
+ std::move(chunk));
+ }
+ }
+ const size_t nToCopy = static_cast<size_t>(
+ std::min(static_cast<unsigned long long>(sizeBytes),
+ region.size() - (iterOffset - offsetToDownload)));
+ memcpy(buffer, region.data() + iterOffset - offsetToDownload, nToCopy);
+ buffer = static_cast<char *>(buffer) + nToCopy;
+ iterOffset += nToCopy;
+ sizeBytes -= nToCopy;
+ if (region.size() < static_cast<size_t>(DOWNLOAD_CHUNK_SIZE) &&
+ sizeBytes != 0) {
+ break;
+ }
+ }
+
+ size_t nRead = static_cast<size_t>(iterOffset - m_pos);
+ m_pos = iterOffset;
+ return nRead;
+}
+
+// ---------------------------------------------------------------------------
+
+bool NetworkFile::seek(unsigned long long offset, int whence) {
+ if (whence == SEEK_SET) {
+ m_pos = offset;
+ } else if (whence == SEEK_CUR) {
+ m_pos += offset;
+ } else {
+ if (offset != 0)
+ return false;
+ m_pos = m_props.size;
+ }
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+unsigned long long NetworkFile::tell() { return m_pos; }
+
+// ---------------------------------------------------------------------------
+
+NetworkFile::~NetworkFile() {
+ if (m_handle) {
+ m_ctx->networking.close(m_ctx, m_handle, m_ctx->networking.user_data);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+void NetworkFile::reassign_context(PJ_CONTEXT *ctx) {
+ m_ctx = ctx;
+ if (m_closeCbk != m_ctx->networking.close) {
+ pj_log(m_ctx, PJ_LOG_ERROR,
+ "Networking close callback has changed following context "
+ "reassignment ! This is highly suspicious");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+#ifdef CURL_ENABLED
+
+struct CurlFileHandle {
+ std::string m_url;
+ CURL *m_handle;
+ std::string m_headers{};
+ std::string m_lastval{};
+ std::string m_useragent{};
+ char m_szCurlErrBuf[CURL_ERROR_SIZE + 1] = {};
+
+ CurlFileHandle(const CurlFileHandle &) = delete;
+ CurlFileHandle &operator=(const CurlFileHandle &) = delete;
+
+ explicit CurlFileHandle(const char *url, CURL *handle);
+ ~CurlFileHandle();
+
+ static PROJ_NETWORK_HANDLE *
+ open(PJ_CONTEXT *, const char *url, unsigned long long offset,
+ size_t size_to_read, void *buffer, size_t *out_size_read,
+ size_t error_string_max_size, char *out_error_string, void *);
+};
+
+// ---------------------------------------------------------------------------
+
+static std::string GetExecutableName() {
+#if defined(__linux)
+ std::string path;
+ path.resize(1024);
+ const auto ret = readlink("/proc/self/exe", &path[0], path.size());
+ if (ret > 0) {
+ path.resize(ret);
+ const auto pos = path.rfind('/');
+ if (pos != std::string::npos) {
+ path = path.substr(pos + 1);
+ }
+ return path;
+ }
+#elif defined(_WIN32)
+ std::string path;
+ path.resize(1024);
+ if (GetModuleFileNameA(nullptr, &path[0],
+ static_cast<DWORD>(path.size()))) {
+ path.resize(strlen(path.c_str()));
+ const auto pos = path.rfind('\\');
+ if (pos != std::string::npos) {
+ path = path.substr(pos + 1);
+ }
+ return path;
+ }
+#elif defined(__MACH__) && defined(__APPLE__)
+ std::string path;
+ path.resize(1024);
+ uint32_t size = static_cast<uint32_t>(path.size());
+ if (_NSGetExecutablePath(&path[0], &size) == 0) {
+ path.resize(strlen(path.c_str()));
+ const auto pos = path.rfind('/');
+ if (pos != std::string::npos) {
+ path = path.substr(pos + 1);
+ }
+ return path;
+ }
+#elif defined(__FreeBSD__)
+ int mib[4];
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC;
+ mib[2] = KERN_PROC_PATHNAME;
+ mib[3] = -1;
+ std::string path;
+ path.resize(1024);
+ size_t size = path.size();
+ if (sysctl(mib, 4, &path[0], &size, nullptr, 0) == 0) {
+ path.resize(strlen(path.c_str()));
+ const auto pos = path.rfind('/');
+ if (pos != std::string::npos) {
+ path = path.substr(pos + 1);
+ }
+ return path;
+ }
+#endif
+
+ return std::string();
+}
+
+// ---------------------------------------------------------------------------
+
+CurlFileHandle::CurlFileHandle(const char *url, CURL *handle)
+ : m_url(url), m_handle(handle) {
+ curl_easy_setopt(handle, CURLOPT_URL, m_url.c_str());
+
+ if (getenv("PROJ_CURL_VERBOSE"))
+ curl_easy_setopt(handle, CURLOPT_VERBOSE, 1);
+
+// CURLOPT_SUPPRESS_CONNECT_HEADERS is defined in curl 7.54.0 or newer.
+#if LIBCURL_VERSION_NUM >= 0x073600
+ curl_easy_setopt(handle, CURLOPT_SUPPRESS_CONNECT_HEADERS, 1L);
+#endif
+
+ // Enable following redirections. Requires libcurl 7.10.1 at least.
+ curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1);
+ curl_easy_setopt(handle, CURLOPT_MAXREDIRS, 10);
+
+ if (getenv("PROJ_UNSAFE_SSL")) {
+ curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0L);
+ curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 0L);
+ }
+
+ curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, m_szCurlErrBuf);
+
+ if (getenv("PROJ_NO_USERAGENT") == nullptr) {
+ m_useragent = "PROJ " STR(PROJ_VERSION_MAJOR) "." STR(
+ PROJ_VERSION_MINOR) "." STR(PROJ_VERSION_PATCH);
+ const auto exeName = GetExecutableName();
+ if (!exeName.empty()) {
+ m_useragent = exeName + " using " + m_useragent;
+ }
+ curl_easy_setopt(handle, CURLOPT_USERAGENT, m_useragent.data());
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+CurlFileHandle::~CurlFileHandle() { curl_easy_cleanup(m_handle); }
+
+// ---------------------------------------------------------------------------
+
+static size_t pj_curl_write_func(void *buffer, size_t count, size_t nmemb,
+ void *req) {
+ const size_t nSize = count * nmemb;
+ auto pStr = static_cast<std::string *>(req);
+ if (pStr->size() + nSize > pStr->capacity()) {
+ // to avoid servers not honouring Range to cause excessive memory
+ // allocation
+ return 0;
+ }
+ pStr->append(static_cast<const char *>(buffer), nSize);
+ return nmemb;
+}
+
+// ---------------------------------------------------------------------------
+
+static double GetNewRetryDelay(int response_code, double dfOldDelay,
+ const char *pszErrBuf,
+ const char *pszCurlError) {
+ if (response_code == 429 || response_code == 500 ||
+ (response_code >= 502 && response_code <= 504) ||
+ // S3 sends some client timeout errors as 400 Client Error
+ (response_code == 400 && pszErrBuf &&
+ strstr(pszErrBuf, "RequestTimeout")) ||
+ (pszCurlError && strstr(pszCurlError, "Connection timed out"))) {
+ // Use an exponential backoff factor of 2 plus some random jitter
+ // We don't care about cryptographic quality randomness, hence:
+ // coverity[dont_call]
+ return dfOldDelay * (2 + rand() * 0.5 / RAND_MAX);
+ } else {
+ return 0;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+constexpr double MIN_RETRY_DELAY_MS = 500;
+constexpr double MAX_RETRY_DELAY_MS = 60000;
+
+PROJ_NETWORK_HANDLE *CurlFileHandle::open(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 *) {
+ CURL *hCurlHandle = curl_easy_init();
+ if (!hCurlHandle)
+ return nullptr;
+
+ auto file =
+ std::unique_ptr<CurlFileHandle>(new CurlFileHandle(url, hCurlHandle));
+
+ double oldDelay = MIN_RETRY_DELAY_MS;
+ std::string headers;
+ std::string body;
+
+ char szBuffer[128];
+ sqlite3_snprintf(sizeof(szBuffer), szBuffer, "%llu-%llu", offset,
+ offset + size_to_read - 1);
+
+ while (true) {
+ curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, szBuffer);
+
+ headers.clear();
+ headers.reserve(16 * 1024);
+ curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA, &headers);
+ curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION,
+ pj_curl_write_func);
+
+ body.clear();
+ body.reserve(size_to_read);
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &body);
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
+ pj_curl_write_func);
+
+ file->m_szCurlErrBuf[0] = '\0';
+
+ curl_easy_perform(hCurlHandle);
+
+ long response_code = 0;
+ curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
+
+ curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA, nullptr);
+ curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION, nullptr);
+
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, nullptr);
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION, nullptr);
+
+ if (response_code == 0 || response_code >= 300) {
+ const double delay =
+ GetNewRetryDelay(static_cast<int>(response_code), oldDelay,
+ body.c_str(), file->m_szCurlErrBuf);
+ if (delay != 0 && delay < MAX_RETRY_DELAY_MS) {
+ pj_log(ctx, PJ_LOG_TRACE,
+ "Got a HTTP %ld error. Retrying in %d ms", response_code,
+ static_cast<int>(delay));
+ sleep_ms(static_cast<int>(delay));
+ oldDelay = delay;
+ } else {
+ if (out_error_string) {
+ if (file->m_szCurlErrBuf[0]) {
+ snprintf(out_error_string, error_string_max_size, "%s",
+ file->m_szCurlErrBuf);
+ } else {
+ snprintf(out_error_string, error_string_max_size,
+ "HTTP error %ld: %s", response_code,
+ body.c_str());
+ }
+ }
+ return nullptr;
+ }
+ } else {
+ break;
+ }
+ }
+
+ if (out_error_string && error_string_max_size) {
+ out_error_string[0] = '\0';
+ }
+
+ if (!body.empty()) {
+ memcpy(buffer, body.data(), std::min(size_to_read, body.size()));
+ }
+ *out_size_read = std::min(size_to_read, body.size());
+
+ file->m_headers = std::move(headers);
+ return reinterpret_cast<PROJ_NETWORK_HANDLE *>(file.release());
+}
+
+// ---------------------------------------------------------------------------
+
+static void pj_curl_close(PJ_CONTEXT *, PROJ_NETWORK_HANDLE *handle,
+ void * /*user_data*/) {
+ delete reinterpret_cast<CurlFileHandle *>(handle);
+}
+
+// ---------------------------------------------------------------------------
+
+static size_t pj_curl_read_range(PJ_CONTEXT *ctx,
+ PROJ_NETWORK_HANDLE *raw_handle,
+ unsigned long long offset, size_t size_to_read,
+ void *buffer, size_t error_string_max_size,
+ char *out_error_string, void *) {
+ auto handle = reinterpret_cast<CurlFileHandle *>(raw_handle);
+ auto hCurlHandle = handle->m_handle;
+
+ double oldDelay = MIN_RETRY_DELAY_MS;
+ std::string headers;
+ std::string body;
+
+ char szBuffer[128];
+ sqlite3_snprintf(sizeof(szBuffer), szBuffer, "%llu-%llu", offset,
+ offset + size_to_read - 1);
+
+ while (true) {
+ curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, szBuffer);
+
+ headers.clear();
+ headers.reserve(16 * 1024);
+ curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA, &headers);
+ curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION,
+ pj_curl_write_func);
+
+ body.clear();
+ body.reserve(size_to_read);
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &body);
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
+ pj_curl_write_func);
+
+ handle->m_szCurlErrBuf[0] = '\0';
+
+ curl_easy_perform(hCurlHandle);
+
+ long response_code = 0;
+ curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
+
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, nullptr);
+ curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION, nullptr);
+
+ if (response_code == 0 || response_code >= 300) {
+ const double delay =
+ GetNewRetryDelay(static_cast<int>(response_code), oldDelay,
+ body.c_str(), handle->m_szCurlErrBuf);
+ if (delay != 0 && delay < MAX_RETRY_DELAY_MS) {
+ pj_log(ctx, PJ_LOG_TRACE,
+ "Got a HTTP %ld error. Retrying in %d ms", response_code,
+ static_cast<int>(delay));
+ sleep_ms(static_cast<int>(delay));
+ oldDelay = delay;
+ } else {
+ if (out_error_string) {
+ if (handle->m_szCurlErrBuf[0]) {
+ snprintf(out_error_string, error_string_max_size, "%s",
+ handle->m_szCurlErrBuf);
+ } else {
+ snprintf(out_error_string, error_string_max_size,
+ "HTTP error %ld: %s", response_code,
+ body.c_str());
+ }
+ }
+ return 0;
+ }
+ } else {
+ break;
+ }
+ }
+ if (out_error_string && error_string_max_size) {
+ out_error_string[0] = '\0';
+ }
+
+ if (!body.empty()) {
+ memcpy(buffer, body.data(), std::min(size_to_read, body.size()));
+ }
+ handle->m_headers = std::move(headers);
+
+ return std::min(size_to_read, body.size());
+}
+
+// ---------------------------------------------------------------------------
+
+static const char *pj_curl_get_header_value(PJ_CONTEXT *,
+ PROJ_NETWORK_HANDLE *raw_handle,
+ const char *header_name, void *) {
+ auto handle = reinterpret_cast<CurlFileHandle *>(raw_handle);
+ auto pos = ci_find(handle->m_headers, header_name);
+ if (pos == std::string::npos)
+ return nullptr;
+ pos += strlen(header_name);
+ const char *c_str = handle->m_headers.c_str();
+ if (c_str[pos] == ':')
+ pos++;
+ while (c_str[pos] == ' ')
+ pos++;
+ auto posEnd = pos;
+ while (c_str[posEnd] != '\r' && c_str[posEnd] != '\n' &&
+ c_str[posEnd] != '\0')
+ posEnd++;
+ handle->m_lastval = handle->m_headers.substr(pos, posEnd - pos);
+ return handle->m_lastval.c_str();
+}
+
+#else
+
+// ---------------------------------------------------------------------------
+
+static PROJ_NETWORK_HANDLE *
+no_op_network_open(PJ_CONTEXT *, const char * /* url */,
+ unsigned long long, /* offset */
+ size_t, /* size to read */
+ void *, /* buffer to update with bytes read*/
+ size_t *, /* output: size actually read */
+ size_t error_string_max_size, char *out_error_string,
+ void * /*user_data*/) {
+ if (out_error_string) {
+ snprintf(out_error_string, error_string_max_size, "%s",
+ "Network functionality not available");
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+static void no_op_network_close(PJ_CONTEXT *, PROJ_NETWORK_HANDLE *,
+ void * /*user_data*/) {}
+
+#endif
+
+// ---------------------------------------------------------------------------
+
+void FileManager::fillDefaultNetworkInterface(PJ_CONTEXT *ctx) {
+#ifdef CURL_ENABLED
+ ctx->networking.open = CurlFileHandle::open;
+ ctx->networking.close = pj_curl_close;
+ ctx->networking.read_range = pj_curl_read_range;
+ ctx->networking.get_header_value = pj_curl_get_header_value;
+#else
+ ctx->networking.open = no_op_network_open;
+ ctx->networking.close = no_op_network_close;
+#endif
+}
+
+// ---------------------------------------------------------------------------
+
+void FileManager::clearMemoryCache() {
+ gNetworkChunkCache.clearMemoryCache();
+ gNetworkFileProperties.clearMemoryCache();
+}
+
+NS_PROJ_END
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+#ifdef WIN32
+static const char dir_chars[] = "/\\";
+#else
+static const char dir_chars[] = "/";
+#endif
+
+static bool is_tilde_slash(const char *name) {
+ return *name == '~' && strchr(dir_chars, name[1]);
+}
+
+static bool is_rel_or_absolute_filename(const char *name) {
+ return strchr(dir_chars, *name) ||
+ (*name == '.' && strchr(dir_chars, name[1])) ||
+ (!strncmp(name, "..", 2) && strchr(dir_chars, name[2])) ||
+ (name[0] != '\0' && name[1] == ':' && strchr(dir_chars, name[2]));
+}
+
+static std::string build_url(PJ_CONTEXT *ctx, const char *name) {
+ if (!is_tilde_slash(name) && !is_rel_or_absolute_filename(name) &&
+ !starts_with(name, "http://") && !starts_with(name, "https://")) {
+ std::string remote_file(pj_context_get_url_endpoint(ctx));
+ if (!remote_file.empty()) {
+ if (remote_file.back() != '/') {
+ remote_file += '/';
+ }
+ remote_file += name;
+ auto pos = remote_file.rfind('.');
+ if (pos + 4 == remote_file.size()) {
+ remote_file = remote_file.substr(0, pos) + ".tif";
+ } else {
+ // For example for resource files like 'alaska'
+ remote_file += ".tif";
+ }
+ }
+ return remote_file;
+ }
+ return name;
+}
+
+// ---------------------------------------------------------------------------
+
+/** Define a custom set of callbacks for network access.
+ *
+ * All callbacks should be provided (non NULL pointers).
+ *
+ * @param ctx PROJ context, or NULL
+ * @param open_cbk Callback to open a remote file given its URL
+ * @param close_cbk Callback to close a remote file.
+ * @param get_header_value_cbk Callback to get HTTP headers
+ * @param read_range_cbk Callback to read a range of bytes inside a remote file.
+ * @param user_data Arbitrary pointer provided by the user, and passed to the
+ * above callbacks. May be NULL.
+ * @return TRUE in case of success.
+ * @since 7.0
+ */
+int proj_context_set_network_callbacks(
+ PJ_CONTEXT *ctx, proj_network_open_cbk_type open_cbk,
+ proj_network_close_cbk_type close_cbk,
+ proj_network_get_header_value_cbk_type get_header_value_cbk,
+ proj_network_read_range_type read_range_cbk, void *user_data) {
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+ if (!open_cbk || !close_cbk || !get_header_value_cbk || !read_range_cbk) {
+ return false;
+ }
+ ctx->networking.open = open_cbk;
+ ctx->networking.close = close_cbk;
+ ctx->networking.get_header_value = get_header_value_cbk;
+ ctx->networking.read_range = read_range_cbk;
+ ctx->networking.user_data = user_data;
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+/** Enable or disable network access.
+*
+* This overrides the default endpoint in the PROJ configuration file or with
+* the PROJ_NETWORK environment variable.
+*
+* @param ctx PROJ context, or NULL
+* @param enable TRUE if network access is allowed.
+* @return TRUE if network access is possible. That is either libcurl is
+* available, or an alternate interface has been set.
+* @since 7.0
+*/
+int proj_context_set_enable_network(PJ_CONTEXT *ctx, int enable) {
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+ // Load ini file, now so as to override its network settings
+ pj_load_ini(ctx);
+ ctx->networking.enabled_env_variable_checked = true;
+ ctx->networking.enabled = enable != FALSE;
+#ifdef CURL_ENABLED
+ return ctx->networking.enabled;
+#else
+ return ctx->networking.enabled &&
+ ctx->networking.open != NS_PROJ::no_op_network_open;
+#endif
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+/** Define the URL endpoint to query for remote grids.
+*
+* This overrides the default endpoint in the PROJ configuration file or with
+* the PROJ_NETWORK_ENDPOINT environment variable.
+*
+* @param ctx PROJ context, or NULL
+* @param url Endpoint URL. Must NOT be NULL.
+* @since 7.0
+*/
+void proj_context_set_url_endpoint(PJ_CONTEXT *ctx, const char *url) {
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+ // Load ini file, now so as to override its network settings
+ pj_load_ini(ctx);
+ ctx->endpoint = url;
+}
+
+// ---------------------------------------------------------------------------
+
+/** Enable or disable the local cache of grid chunks
+*
+* This overrides the setting in the PROJ configuration file.
+*
+* @param ctx PROJ context, or NULL
+* @param enabled TRUE if the cache is enabled.
+* @since 7.0
+*/
+void proj_grid_cache_set_enable(PJ_CONTEXT *ctx, int enabled) {
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+ // Load ini file, now so as to override its settings
+ pj_load_ini(ctx);
+ ctx->gridChunkCache.enabled = enabled != FALSE;
+}
+
+// ---------------------------------------------------------------------------
+
+/** Override, for the considered context, the path and file of the local
+* cache of grid chunks.
+*
+* @param ctx PROJ context, or NULL
+* @param fullname Full name to the cache (encoded in UTF-8). If set to NULL,
+* caching will be disabled.
+* @since 7.0
+*/
+void proj_grid_cache_set_filename(PJ_CONTEXT *ctx, const char *fullname) {
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+ // Load ini file, now so as to override its settings
+ pj_load_ini(ctx);
+ ctx->gridChunkCache.filename = fullname ? fullname : std::string();
+}
+
+// ---------------------------------------------------------------------------
+
+/** Override, for the considered context, the maximum size of the local
+* cache of grid chunks.
+*
+* @param ctx PROJ context, or NULL
+* @param max_size_MB Maximum size, in mega-bytes (1024*1024 bytes), or
+* negative value to set unlimited size.
+* @since 7.0
+*/
+void proj_grid_cache_set_max_size(PJ_CONTEXT *ctx, int max_size_MB) {
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+ // Load ini file, now so as to override its settings
+ pj_load_ini(ctx);
+ ctx->gridChunkCache.max_size =
+ max_size_MB < 0 ? -1
+ : static_cast<long long>(max_size_MB) * 1024 * 1024;
+ if (max_size_MB == 0) {
+ // For debug purposes only
+ const char *env_var = getenv("PROJ_GRID_CACHE_MAX_SIZE_BYTES");
+ if (env_var && env_var[0] != '\0') {
+ ctx->gridChunkCache.max_size = atoi(env_var);
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+/** Override, for the considered context, the time-to-live delay for
+* re-checking if the cached properties of files are still up-to-date.
+*
+* @param ctx PROJ context, or NULL
+* @param ttl_seconds Delay in seconds. Use negative value for no expiration.
+* @since 7.0
+*/
+void proj_grid_cache_set_ttl(PJ_CONTEXT *ctx, int ttl_seconds) {
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+ // Load ini file, now so as to override its settings
+ pj_load_ini(ctx);
+ ctx->gridChunkCache.ttl = ttl_seconds;
+}
+
+// ---------------------------------------------------------------------------
+
+/** Clear the local cache of grid chunks.
+*
+* @param ctx PROJ context, or NULL
+* @since 7.0
+*/
+void proj_grid_cache_clear(PJ_CONTEXT *ctx) {
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+ NS_PROJ::gNetworkChunkCache.clearDiskChunkCache(ctx);
+}
+
+// ---------------------------------------------------------------------------
+
+/** Return if a file must be downloaded or is already available in the
+ * PROJ user-writable directory.
+ *
+ * The file will be determinted to have to be downloaded if it does not exist
+ * yet in the user-writable directory, or if it is determined that a more recent
+ * version exists. To determine if a more recent version exists, PROJ will
+ * use the "downloaded_file_properties" table of its grid cache database.
+ * Consequently files manually placed in the user-writable
+ * directory without using this function would be considered as
+ * non-existing/obsolete and would be unconditionnaly downloaded again.
+ *
+ * This function can only be used if networking is enabled, and either
+ * the default curl network API or a custom one have been installed.
+ *
+ * @param ctx PROJ context, or NULL
+ * @param url_or_filename URL or filename (without directory component)
+ * @param ignore_ttl_setting If set to FALSE, PROJ will only check the
+ * recentness of an already downloaded file, if
+ * the delay between the last time it has been
+ * verified and the current time exceeds the TTL
+ * setting. This can save network accesses.
+ * If set to TRUE, PROJ will unconditionnally
+ * check from the server the recentness of the file.
+ * @return TRUE if the file must be downloaded with proj_download_file()
+ * @since 7.0
+ */
+
+int proj_is_download_needed(PJ_CONTEXT *ctx, const char *url_or_filename,
+ int ignore_ttl_setting) {
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+ if (!pj_context_is_network_enabled(ctx)) {
+ pj_log(ctx, PJ_LOG_ERROR, "Networking capabilities are not enabled");
+ return false;
+ }
+
+ const auto url(build_url(ctx, url_or_filename));
+ const char *filename = strrchr(url.c_str(), '/');
+ if (filename == nullptr)
+ return false;
+ const auto localFilename(
+ pj_context_get_user_writable_directory(ctx, false) + filename);
+
+ auto f = NS_PROJ::FileManager::open(ctx, localFilename.c_str(),
+ NS_PROJ::FileAccess::READ_ONLY);
+ if (!f) {
+ return true;
+ }
+ f.reset();
+
+ auto diskCache = NS_PROJ::DiskChunkCache::open(ctx);
+ if (!diskCache)
+ return false;
+ auto stmt =
+ diskCache->prepare("SELECT lastChecked, fileSize, lastModified, etag "
+ "FROM downloaded_file_properties WHERE url = ?");
+ if (!stmt)
+ return true;
+ stmt->bindText(url.c_str());
+ if (stmt->execute() != SQLITE_ROW) {
+ return true;
+ }
+
+ NS_PROJ::FileProperties cachedProps;
+ cachedProps.lastChecked = stmt->getInt64();
+ cachedProps.size = stmt->getInt64();
+ const char *lastModified = stmt->getText();
+ cachedProps.lastModified = lastModified ? lastModified : std::string();
+ const char *etag = stmt->getText();
+ cachedProps.etag = etag ? etag : std::string();
+
+ if (!ignore_ttl_setting) {
+ const auto ttl = NS_PROJ::pj_context_get_grid_cache_ttl(ctx);
+ if (ttl > 0) {
+ time_t curTime;
+ time(&curTime);
+ if (curTime > cachedProps.lastChecked + ttl) {
+
+ unsigned char dummy;
+ size_t size_read = 0;
+ std::string errorBuffer;
+ errorBuffer.resize(1024);
+ auto handle = ctx->networking.open(
+ ctx, url.c_str(), 0, 1, &dummy, &size_read,
+ errorBuffer.size(), &errorBuffer[0],
+ ctx->networking.user_data);
+ if (!handle) {
+ errorBuffer.resize(strlen(errorBuffer.data()));
+ pj_log(ctx, PJ_LOG_ERROR, "Cannot open %s: %s", url.c_str(),
+ errorBuffer.c_str());
+ return false;
+ }
+ NS_PROJ::FileProperties props;
+ if (!NS_PROJ::NetworkFile::get_props_from_headers(ctx, handle,
+ props)) {
+ ctx->networking.close(ctx, handle,
+ ctx->networking.user_data);
+ return false;
+ }
+ ctx->networking.close(ctx, handle, ctx->networking.user_data);
+
+ if (props.size != cachedProps.size ||
+ props.lastModified != cachedProps.lastModified ||
+ props.etag != cachedProps.etag) {
+ return true;
+ }
+
+ stmt = diskCache->prepare(
+ "UPDATE downloaded_file_properties SET lastChecked = ? "
+ "WHERE url = ?");
+ if (!stmt)
+ return false;
+ stmt->bindInt64(curTime);
+ stmt->bindText(url.c_str());
+ if (stmt->execute() != SQLITE_DONE) {
+ auto hDB = diskCache->handle();
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return false;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+// ---------------------------------------------------------------------------
+
+/** Download a file in the PROJ user-writable directory.
+ *
+ * The file will only be downloaded if it does not exist yet in the
+ * user-writable directory, or if it is determined that a more recent
+ * version exists. To determine if a more recent version exists, PROJ will
+ * use the "downloaded_file_properties" table of its grid cache database.
+ * Consequently files manually placed in the user-writable
+ * directory without using this function would be considered as
+ * non-existing/obsolete and would be unconditionnaly downloaded again.
+ *
+ * This function can only be used if networking is enabled, and either
+ * the default curl network API or a custom one have been installed.
+ *
+ * @param ctx PROJ context, or NULL
+ * @param url_or_filename URL or filename (without directory component)
+ * @param ignore_ttl_setting If set to FALSE, PROJ will only check the
+ * recentness of an already downloaded file, if
+ * the delay between the last time it has been
+ * verified and the current time exceeds the TTL
+ * setting. This can save network accesses.
+ * If set to TRUE, PROJ will unconditionnally
+ * check from the server the recentness of the file.
+ * @param progress_cbk Progress callback, or NULL.
+ * The passed percentage is in the [0, 1] range.
+ * The progress callback must return TRUE
+ * if download must be continued.
+ * @param user_data User data to provide to the progress callback, or NULL
+ * @return TRUE if the download was successful (or not needed)
+ * @since 7.0
+ */
+
+int proj_download_file(PJ_CONTEXT *ctx, const char *url_or_filename,
+ int ignore_ttl_setting,
+ int (*progress_cbk)(PJ_CONTEXT *, double pct,
+ void *user_data),
+ void *user_data) {
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+ if (!pj_context_is_network_enabled(ctx)) {
+ pj_log(ctx, PJ_LOG_ERROR, "Networking capabilities are not enabled");
+ return false;
+ }
+ if (!proj_is_download_needed(ctx, url_or_filename, ignore_ttl_setting)) {
+ return true;
+ }
+
+ const auto url(build_url(ctx, url_or_filename));
+ const char *filename = strrchr(url.c_str(), '/');
+ if (filename == nullptr)
+ return false;
+ const auto localFilename(pj_context_get_user_writable_directory(ctx, true) +
+ filename);
+
+#ifdef _WIN32
+ const int nPID = GetCurrentProcessId();
+#else
+ const int nPID = getpid();
+#endif
+ char szUniqueSuffix[128];
+ snprintf(szUniqueSuffix, sizeof(szUniqueSuffix), "%d_%p", nPID, &url);
+ const auto localFilenameTmp(localFilename + szUniqueSuffix);
+ auto f = NS_PROJ::FileManager::open(ctx, localFilenameTmp.c_str(),
+ NS_PROJ::FileAccess::CREATE);
+ if (!f) {
+ pj_log(ctx, PJ_LOG_ERROR, "Cannot create %s", localFilenameTmp.c_str());
+ return false;
+ }
+
+ constexpr size_t FULL_FILE_CHUNK_SIZE = 1024 * 1024;
+ std::vector<unsigned char> buffer(FULL_FILE_CHUNK_SIZE);
+ // For testing purposes only
+ const char *env_var_PROJ_FULL_FILE_CHUNK_SIZE =
+ getenv("PROJ_FULL_FILE_CHUNK_SIZE");
+ if (env_var_PROJ_FULL_FILE_CHUNK_SIZE &&
+ env_var_PROJ_FULL_FILE_CHUNK_SIZE[0] != '\0') {
+ buffer.resize(atoi(env_var_PROJ_FULL_FILE_CHUNK_SIZE));
+ }
+ size_t size_read = 0;
+ std::string errorBuffer;
+ errorBuffer.resize(1024);
+ auto handle = ctx->networking.open(
+ ctx, url.c_str(), 0, buffer.size(), &buffer[0], &size_read,
+ errorBuffer.size(), &errorBuffer[0], ctx->networking.user_data);
+ if (!handle) {
+ errorBuffer.resize(strlen(errorBuffer.data()));
+ pj_log(ctx, PJ_LOG_ERROR, "Cannot open %s: %s", url.c_str(),
+ errorBuffer.c_str());
+ f.reset();
+ NS_PROJ::FileManager::unlink(ctx, localFilenameTmp.c_str());
+ return false;
+ }
+
+ time_t curTime;
+ time(&curTime);
+ NS_PROJ::FileProperties props;
+ if (!NS_PROJ::NetworkFile::get_props_from_headers(ctx, handle, props)) {
+ ctx->networking.close(ctx, handle, ctx->networking.user_data);
+ f.reset();
+ NS_PROJ::FileManager::unlink(ctx, localFilenameTmp.c_str());
+ return false;
+ }
+
+ if (size_read <
+ std::min(static_cast<unsigned long long>(buffer.size()), props.size)) {
+ pj_log(ctx, PJ_LOG_ERROR, "Did not get as many bytes as expected");
+ ctx->networking.close(ctx, handle, ctx->networking.user_data);
+ f.reset();
+ NS_PROJ::FileManager::unlink(ctx, localFilenameTmp.c_str());
+ return false;
+ }
+ if (f->write(buffer.data(), size_read) != size_read) {
+ pj_log(ctx, PJ_LOG_ERROR, "Write error");
+ ctx->networking.close(ctx, handle, ctx->networking.user_data);
+ f.reset();
+ NS_PROJ::FileManager::unlink(ctx, localFilenameTmp.c_str());
+ return false;
+ }
+
+ unsigned long long totalDownloaded = size_read;
+ while (totalDownloaded < props.size) {
+ if (totalDownloaded + buffer.size() > props.size) {
+ buffer.resize(static_cast<size_t>(props.size - totalDownloaded));
+ }
+ errorBuffer.resize(1024);
+ size_read = ctx->networking.read_range(
+ ctx, handle, totalDownloaded, buffer.size(), &buffer[0],
+ errorBuffer.size(), &errorBuffer[0], ctx->networking.user_data);
+
+ if (size_read < buffer.size()) {
+ pj_log(ctx, PJ_LOG_ERROR, "Did not get as many bytes as expected");
+ ctx->networking.close(ctx, handle, ctx->networking.user_data);
+ f.reset();
+ NS_PROJ::FileManager::unlink(ctx, localFilenameTmp.c_str());
+ return false;
+ }
+ if (f->write(buffer.data(), size_read) != size_read) {
+ pj_log(ctx, PJ_LOG_ERROR, "Write error");
+ ctx->networking.close(ctx, handle, ctx->networking.user_data);
+ f.reset();
+ NS_PROJ::FileManager::unlink(ctx, localFilenameTmp.c_str());
+ return false;
+ }
+
+ totalDownloaded += size_read;
+ if (progress_cbk &&
+ !progress_cbk(ctx, double(totalDownloaded) / props.size,
+ user_data)) {
+ ctx->networking.close(ctx, handle, ctx->networking.user_data);
+ f.reset();
+ NS_PROJ::FileManager::unlink(ctx, localFilenameTmp.c_str());
+ return false;
+ }
+ }
+
+ ctx->networking.close(ctx, handle, ctx->networking.user_data);
+ f.reset();
+ NS_PROJ::FileManager::unlink(ctx, localFilename.c_str());
+ if (!NS_PROJ::FileManager::rename(ctx, localFilenameTmp.c_str(),
+ localFilename.c_str())) {
+ pj_log(ctx, PJ_LOG_ERROR, "Cannot rename %s to %s",
+ localFilenameTmp.c_str(), localFilename.c_str());
+ return false;
+ }
+
+ auto diskCache = NS_PROJ::DiskChunkCache::open(ctx);
+ if (!diskCache)
+ return false;
+ auto stmt =
+ diskCache->prepare("SELECT lastChecked, fileSize, lastModified, etag "
+ "FROM downloaded_file_properties WHERE url = ?");
+ if (!stmt)
+ return false;
+ stmt->bindText(url.c_str());
+
+ props.lastChecked = curTime;
+ auto hDB = diskCache->handle();
+
+ if (stmt->execute() == SQLITE_ROW) {
+ stmt = diskCache->prepare(
+ "UPDATE downloaded_file_properties SET lastChecked = ?, "
+ "fileSize = ?, lastModified = ?, etag = ? "
+ "WHERE url = ?");
+ if (!stmt)
+ return false;
+ stmt->bindInt64(props.lastChecked);
+ stmt->bindInt64(props.size);
+ if (props.lastModified.empty())
+ stmt->bindNull();
+ else
+ stmt->bindText(props.lastModified.c_str());
+ if (props.etag.empty())
+ stmt->bindNull();
+ else
+ stmt->bindText(props.etag.c_str());
+ stmt->bindText(url.c_str());
+ if (stmt->execute() != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return false;
+ }
+ } else {
+ stmt = diskCache->prepare(
+ "INSERT INTO downloaded_file_properties (url, lastChecked, "
+ "fileSize, lastModified, etag) VALUES "
+ "(?,?,?,?,?)");
+ if (!stmt)
+ return false;
+ stmt->bindText(url.c_str());
+ stmt->bindInt64(props.lastChecked);
+ stmt->bindInt64(props.size);
+ if (props.lastModified.empty())
+ stmt->bindNull();
+ else
+ stmt->bindText(props.lastModified.c_str());
+ if (props.etag.empty())
+ stmt->bindNull();
+ else
+ stmt->bindText(props.etag.c_str());
+ if (stmt->execute() != SQLITE_DONE) {
+ pj_log(ctx, PJ_LOG_ERROR, "%s", sqlite3_errmsg(hDB));
+ return false;
+ }
+ }
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+bool pj_context_is_network_enabled(PJ_CONTEXT *ctx) {
+ if (ctx == nullptr) {
+ ctx = pj_get_default_ctx();
+ }
+ if (ctx->networking.enabled_env_variable_checked) {
+ return ctx->networking.enabled;
+ }
+ const char *enabled = getenv("PROJ_NETWORK");
+ if (enabled && enabled[0] != '\0') {
+ ctx->networking.enabled = ci_equal(enabled, "ON") ||
+ ci_equal(enabled, "YES") ||
+ ci_equal(enabled, "TRUE");
+ }
+ pj_load_ini(ctx);
+ ctx->networking.enabled_env_variable_checked = true;
+ return ctx->networking.enabled;
+}
+
+// ---------------------------------------------------------------------------
+
+std::string pj_context_get_grid_cache_filename(PJ_CONTEXT *ctx) {
+ pj_load_ini(ctx);
+ if (!ctx->gridChunkCache.filename.empty()) {
+ return ctx->gridChunkCache.filename;
+ }
+ const std::string path(pj_context_get_user_writable_directory(ctx, true));
+ ctx->gridChunkCache.filename = path + "/cache.db";
+ return ctx->gridChunkCache.filename;
+}
+
+//! @endcond
diff --git a/src/open_lib.cpp b/src/open_lib.cpp
deleted file mode 100644
index 24c31033..00000000
--- a/src/open_lib.cpp
+++ /dev/null
@@ -1,359 +0,0 @@
-/******************************************************************************
- * Project: PROJ.4
- * Purpose: Implementation of pj_open_lib(), and pj_set_finder(). These
- * provide a standard interface for opening projections support
- * data files.
- * Author: Gerald Evenden, Frank Warmerdam <warmerdam@pobox.com>
- *
- ******************************************************************************
- * Copyright (c) 1995, Gerald Evenden
- * Copyright (c) 2002, Frank Warmerdam <warmerdam@pobox.com>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included
- * in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- *****************************************************************************/
-
-#define PJ_LIB__
-
-#ifndef FROM_PROJ_CPP
-#define FROM_PROJ_CPP
-#endif
-
-#include <assert.h>
-#include <errno.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "proj/internal/internal.hpp"
-
-#include "proj_internal.h"
-
-static const char * proj_lib_name =
-#ifdef PROJ_LIB
-PROJ_LIB;
-#else
-nullptr;
-#endif
-
-/************************************************************************/
-/* pj_set_finder() */
-/************************************************************************/
-
-void pj_set_finder( const char *(*new_finder)(const char *) )
-
-{
- auto ctx = pj_get_default_ctx();
- if( ctx ) {
- ctx->file_finder_legacy = new_finder;
- }
-}
-
-/************************************************************************/
-/* proj_context_set_file_finder() */
-/************************************************************************/
-
-/** \brief Assign a file finder callback to a context.
- *
- * This callback will be used whenever PROJ must open one of its resource files
- * (proj.db database, grids, etc...)
- *
- * The callback will be called with the context currently in use at the moment
- * where it is used (not necessarily the one provided during this call), and
- * with the provided user_data (which may be NULL).
- * The user_data must remain valid during the whole lifetime of the context.
- *
- * A finder set on the default context will be inherited by contexts created
- * later.
- *
- * @param ctx PROJ context, or NULL for the default context.
- * @param finder Finder callback. May be NULL
- * @param user_data User data provided to the finder callback. May be NULL.
- *
- * @since PROJ 6.0
- */
-void proj_context_set_file_finder(PJ_CONTEXT *ctx, proj_file_finder finder,
- void* user_data)
-{
- if( !ctx )
- ctx = pj_get_default_ctx();
- if( !ctx )
- return;
- ctx->file_finder = finder;
- ctx->file_finder_user_data = user_data;
-}
-
-/************************************************************************/
-/* proj_context_set_search_paths() */
-/************************************************************************/
-
-
-/** \brief Sets search paths.
- *
- * Those search paths will be used whenever PROJ must open one of its resource files
- * (proj.db database, grids, etc...)
- *
- * If set on the default context, they will be inherited by contexts created
- * later.
- *
- * @param ctx PROJ context, or NULL for the default context.
- * @param count_paths Number of paths. 0 if paths == NULL.
- * @param paths Paths. May be NULL.
- *
- * @since PROJ 6.0
- */
-void proj_context_set_search_paths(PJ_CONTEXT *ctx,
- int count_paths,
- const char* const* paths)
-{
- if( !ctx )
- ctx = pj_get_default_ctx();
- if( !ctx )
- return;
- try {
- std::vector<std::string> vector_of_paths;
- for (int i = 0; i < count_paths; i++)
- {
- vector_of_paths.emplace_back(paths[i]);
- }
- ctx->set_search_paths(vector_of_paths);
- } catch( const std::exception& )
- {
- }
-}
-
-/************************************************************************/
-/* pj_set_searchpath() */
-/* */
-/* Path control for callers that can't practically provide */
-/* pj_set_finder() style callbacks. Call with (0,NULL) as args */
-/* to clear the searchpath set. */
-/************************************************************************/
-
-void pj_set_searchpath ( int count, const char **path )
-{
- proj_context_set_search_paths( nullptr, count, const_cast<const char* const*>(path) );
-}
-
-#ifdef _WIN32
-#include <windows.h>
-#include <sys/stat.h>
-static const char *get_path_from_win32_projlib(const char *name, std::string& out) {
- /* Check if proj.db lieves in a share/proj dir parallel to bin/proj.dll */
- /* Based in https://stackoverflow.com/questions/9112893/how-to-get-path-to-executable-in-c-running-on-windows */
-
- DWORD path_size = 1024;
-
- for (;;) {
- out.resize(path_size);
- memset(&out[0], 0, path_size);
- DWORD result = GetModuleFileNameA(nullptr, &out[0], path_size - 1);
- DWORD last_error = GetLastError();
-
- if (result == 0) {
- return nullptr;
- }
- else if (result == path_size - 1) {
- if (ERROR_INSUFFICIENT_BUFFER != last_error) {
- return nullptr;
- }
- path_size = path_size * 2;
- }
- else {
- break;
- }
- }
- // Now remove the program's name. It was (example) "C:\programs\gmt6\bin\gdal_translate.exe"
- size_t k = strlen(out.c_str());
- while (k > 0 && out[--k] != '\\') {}
- out.resize(k);
-
- out += "/../share/proj/";
- out += name;
-
- struct stat fileInfo;
- if (stat(out.c_str(), &fileInfo) == 0) // Check if file exists (probably there are simpler ways)
- return out.c_str();
- else {
- return nullptr;
- }
-}
-#endif
-
-/************************************************************************/
-/* pj_open_lib_ex() */
-/************************************************************************/
-
-static PAFile
-pj_open_lib_ex(projCtx ctx, const char *name, const char *mode,
- char* out_full_filename, size_t out_full_filename_size) {
- try {
- std::string fname;
- const char *sysname = nullptr;
- PAFile fid = nullptr;
-#ifdef WIN32
- static const char dir_chars[] = "/\\";
- const char dirSeparator = ';';
-#else
- static const char dir_chars[] = "/";
- const char dirSeparator = ':';
-#endif
-
- if( ctx == nullptr ) {
- ctx = pj_get_default_ctx();
- }
-
- if( out_full_filename != nullptr && out_full_filename_size > 0 )
- out_full_filename[0] = '\0';
-
- /* check if ~/name */
- if (*name == '~' && strchr(dir_chars,name[1]) )
- if ((sysname = getenv("HOME")) != nullptr) {
- fname = sysname;
- fname += DIR_CHAR;
- fname += name;
- sysname = fname.c_str();
- } else
- return nullptr;
-
- /* or fixed path: /name, ./name or ../name */
- else if (strchr(dir_chars,*name)
- || (*name == '.' && strchr(dir_chars,name[1]))
- || (!strncmp(name, "..", 2) && strchr(dir_chars,name[2]))
- || (name[0] != '\0' && name[1] == ':' && strchr(dir_chars,name[2])) )
- sysname = name;
-
- /* or try to use application provided file finder */
- else if( ctx->file_finder != nullptr && (sysname = ctx->file_finder( ctx, name, ctx->file_finder_user_data )) != nullptr )
- ;
-
- else if( ctx->file_finder_legacy != nullptr && (sysname = ctx->file_finder_legacy( name )) != nullptr )
- ;
-
- /* The user has search paths set */
- else if( !ctx->search_paths.empty() ) {
- for( const auto& path: ctx->search_paths ) {
- try {
- fname = path;
- fname += DIR_CHAR;
- fname += name;
- sysname = fname.c_str();
- fid = pj_ctx_fopen(ctx, sysname, mode);
- } catch( const std::exception& )
- {
- }
- if( fid )
- break;
- }
- }
- /* if is environment PROJ_LIB defined */
- else if ((sysname = getenv("PROJ_LIB")) != nullptr) {
- auto paths = NS_PROJ::internal::split(std::string(sysname), dirSeparator);
- for( const auto& path: paths ) {
- fname = path;
- fname += DIR_CHAR;
- fname += name;
- sysname = fname.c_str();
- fid = pj_ctx_fopen(ctx, sysname, mode);
- if( fid )
- break;
- }
-#ifdef _WIN32
- /* check if it lives in a ../share/proj dir of the proj dll */
- } else if ((sysname = get_path_from_win32_projlib(name, fname)) != nullptr) {
-#endif
- /* or hardcoded path */
- } else if ((sysname = proj_lib_name) != nullptr) {
- fname = sysname;
- fname += DIR_CHAR;
- fname += name;
- sysname = fname.c_str();
- /* just try it bare bones */
- } else {
- sysname = name;
- }
-
- assert(sysname); // to make Coverity Scan happy
- if ( fid != nullptr || (fid = pj_ctx_fopen(ctx, sysname, mode)) != nullptr)
- {
- if( out_full_filename != nullptr && out_full_filename_size > 0 )
- {
- // cppcheck-suppress nullPointer
- strncpy(out_full_filename, sysname, out_full_filename_size);
- out_full_filename[out_full_filename_size-1] = '\0';
- }
- errno = 0;
- }
-
- if( ctx->last_errno == 0 && errno != 0 )
- pj_ctx_set_errno( ctx, errno );
-
- pj_log( ctx, PJ_LOG_DEBUG_MAJOR,
- "pj_open_lib(%s): call fopen(%s) - %s",
- name, sysname,
- fid == nullptr ? "failed" : "succeeded" );
-
- return(fid);
- }
- catch( const std::exception& ) {
-
- pj_log( ctx, PJ_LOG_DEBUG_MAJOR,
- "pj_open_lib(%s): out of memory",
- name );
-
- return nullptr;
- }
-}
-
-/************************************************************************/
-/* pj_open_lib() */
-/************************************************************************/
-
-PAFile
-pj_open_lib(projCtx ctx, const char *name, const char *mode) {
- return pj_open_lib_ex(ctx, name, mode, nullptr, 0);
-}
-
-/************************************************************************/
-/* pj_find_file() */
-/************************************************************************/
-
-/** Returns the full filename corresponding to a proj resource file specified
- * as a short filename.
- *
- * @param ctx context.
- * @param short_filename short filename (e.g. egm96_15.gtx). Must not be NULL.
- * @param out_full_filename output buffer, of size out_full_filename_size, that
- * will receive the full filename on success.
- * Will be zero-terminated.
- * @param out_full_filename_size size of out_full_filename.
- * @return 1 if the file was found, 0 otherwise.
- */
-int pj_find_file(projCtx ctx, const char *short_filename,
- char* out_full_filename, size_t out_full_filename_size)
-{
- PAFile f = pj_open_lib_ex(ctx, short_filename, "rb", out_full_filename,
- out_full_filename_size);
- if( f != nullptr )
- {
- pj_ctx_fclose(ctx, f);
- return 1;
- }
- return 0;
-}
diff --git a/src/pipeline.cpp b/src/pipeline.cpp
index 96767143..511a69fe 100644
--- a/src/pipeline.cpp
+++ b/src/pipeline.cpp
@@ -155,7 +155,7 @@ static PJ_LPZ pipeline_reverse_3d (PJ_XYZ xyz, PJ *P);
static PJ_XY pipeline_forward (PJ_LP lp, PJ *P);
static PJ_LP pipeline_reverse (PJ_XY xy, PJ *P);
-void pj_pipeline_assign_context_to_steps( PJ* P, PJ_CONTEXT* ctx )
+static void pipeline_reassign_context( PJ* P, PJ_CONTEXT* ctx )
{
auto pipeline = static_cast<struct Pipeline*>(P->opaque);
for( auto& step: pipeline->steps )
@@ -170,6 +170,9 @@ static PJ_COORD pipeline_forward_4d (PJ_COORD point, PJ *P) {
if( !step.omit_fwd )
{
point = proj_trans (step.pj, PJ_FWD, point);
+ if( point.xyzt.x == HUGE_VAL ) {
+ break;
+ }
}
}
@@ -186,6 +189,9 @@ static PJ_COORD pipeline_reverse_4d (PJ_COORD point, PJ *P) {
if( !step.omit_inv )
{
point = proj_trans (step.pj, PJ_INV, point);
+ if( point.xyzt.x == HUGE_VAL ) {
+ break;
+ }
}
}
@@ -204,6 +210,9 @@ static PJ_XYZ pipeline_forward_3d (PJ_LPZ lpz, PJ *P) {
if( !step.omit_fwd )
{
point = pj_approx_3D_trans (step.pj, PJ_FWD, point);
+ if( point.xyzt.x == HUGE_VAL ) {
+ break;
+ }
}
}
@@ -222,6 +231,9 @@ static PJ_LPZ pipeline_reverse_3d (PJ_XYZ xyz, PJ *P) {
if( !step.omit_inv )
{
point = proj_trans (step.pj, PJ_INV, point);
+ if( point.xyzt.x == HUGE_VAL ) {
+ break;
+ }
}
}
@@ -240,6 +252,9 @@ static PJ_XY pipeline_forward (PJ_LP lp, PJ *P) {
if( !step.omit_fwd )
{
point = pj_approx_2D_trans (step.pj, PJ_FWD, point);
+ if( point.xyzt.x == HUGE_VAL ) {
+ break;
+ }
}
}
@@ -258,6 +273,9 @@ static PJ_LP pipeline_reverse (PJ_XY xy, PJ *P) {
if( !step.omit_inv )
{
point = pj_approx_2D_trans (step.pj, PJ_INV, point);
+ if( point.xyzt.x == HUGE_VAL ) {
+ break;
+ }
}
}
@@ -413,7 +431,7 @@ PJ *OPERATION(pipeline,0) {
P->fwd = pipeline_forward;
P->inv = pipeline_reverse;
P->destructor = destructor;
- P->is_pipeline = 1;
+ P->reassign_context = pipeline_reassign_context;
/* Currently, the pipeline driver is a raw bit mover, enabling other operations */
/* to collaborate efficiently. All prep/fin stuff is done at the step levels. */
diff --git a/src/pj_list.h b/src/pj_list.h
index 0923bba8..9798a36b 100644
--- a/src/pj_list.h
+++ b/src/pj_list.h
@@ -170,3 +170,4 @@ PROJ_HEAD(weren, "Werenskiold I")
PROJ_HEAD(wink1, "Winkel I")
PROJ_HEAD(wink2, "Winkel II")
PROJ_HEAD(wintri, "Winkel Tripel")
+PROJ_HEAD(xyzgridshift, "XYZ grid shift")
diff --git a/src/proj.h b/src/proj.h
index 410f1e71..fbed71b0 100644
--- a/src/proj.h
+++ b/src/proj.h
@@ -353,6 +353,166 @@ void PROJ_DLL proj_context_set_search_paths(PJ_CONTEXT *ctx, int count_paths, co
void PROJ_DLL proj_context_use_proj4_init_rules(PJ_CONTEXT *ctx, int enable);
int PROJ_DLL proj_context_get_use_proj4_init_rules(PJ_CONTEXT *ctx, int from_legacy_code_path);
+/*! @endcond */
+
+/** Opaque structure for PROJ for a file handle. Implementations might cast it to their
+ * structure/class of choice. */
+typedef struct PROJ_FILE_HANDLE PROJ_FILE_HANDLE;
+
+/** Open access / mode */
+typedef enum PROJ_OPEN_ACCESS
+{
+ /** Read-only access. Equivalent to "rb" */
+ PROJ_OPEN_ACCESS_READ_ONLY,
+
+ /** Read-update access. File should be created if not existing. Equivalent to "r+b" */
+ PROJ_OPEN_ACCESS_READ_UPDATE,
+
+ /** Create access. File should be truncated to 0-byte if already existing. Equivalent to "w+b" */
+ PROJ_OPEN_ACCESS_CREATE
+} PROJ_OPEN_ACCESS;
+
+/** File API callbacks */
+typedef struct PROJ_FILE_API
+{
+ /** Version of this structure. Should be set to 1 currently. */
+ int version;
+
+ /** Open file. Return NULL if error */
+ PROJ_FILE_HANDLE* (*open_cbk)(PJ_CONTEXT *ctx, const char *filename, PROJ_OPEN_ACCESS access, void* user_data);
+
+ /** Read sizeBytes into buffer from current position and return number of bytes read */
+ size_t (*read_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE*, void* buffer, size_t sizeBytes, void* user_data);
+
+ /** Write sizeBytes into buffer from current position and return number of bytes written */
+ size_t (*write_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE*, const void* buffer, size_t sizeBytes, void* user_data);
+
+ /** Seek to offset using whence=SEEK_SET/SEEK_CUR/SEEK_END. Return TRUE in case of success */
+ int (*seek_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE*, long long offset, int whence, void* user_data);
+
+ /** Return current file position */
+ unsigned long long (*tell_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE*, void* user_data);
+
+ /** Close file */
+ void (*close_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE*, void* user_data);
+
+ /** Return TRUE if a file exists */
+ int (*exists_cbk)(PJ_CONTEXT *ctx, const char *filename, void* user_data);
+
+ /** Return TRUE if directory exists or could be created */
+ int (*mkdir_cbk)(PJ_CONTEXT *ctx, const char *filename, void* user_data);
+
+ /** Return TRUE if file could be removed */
+ int (*unlink_cbk)(PJ_CONTEXT *ctx, const char *filename, void* user_data);
+
+ /** Return TRUE if file could be renamed */
+ int (*rename_cbk)(PJ_CONTEXT *ctx, const char *oldPath, const char *newPath, void* user_data);
+} PROJ_FILE_API;
+
+int PROJ_DLL proj_context_set_fileapi(
+ PJ_CONTEXT* ctx, const PROJ_FILE_API* fileapi, void* user_data);
+
+void PROJ_DLL proj_context_set_sqlite3_vfs_name(PJ_CONTEXT* ctx, const char* name);
+
+/** Opaque structure for PROJ for a network handle. Implementations might cast it to their
+ * structure/class of choice. */
+typedef struct PROJ_NETWORK_HANDLE PROJ_NETWORK_HANDLE;
+
+/** Network access: open callback
+ *
+ * Should try to read the size_to_read first bytes at the specified offset of
+ * the file given by URL url,
+ * and write them to buffer. *out_size_read should be updated with the actual
+ * amount of bytes read (== size_to_read if the file is larger than size_to_read).
+ * During this read, the implementation should make sure to store the HTTP
+ * headers from the server response to be able to respond to
+ * proj_network_get_header_value_cbk_type callback.
+ *
+ * error_string_max_size should be the maximum size that can be written into
+ * the out_error_string buffer (including terminating nul character).
+ *
+ * @return a non-NULL opaque handle in case of success.
+ */
+typedef PROJ_NETWORK_HANDLE* (*proj_network_open_cbk_type)(
+ PJ_CONTEXT* ctx,
+ const char* url,
+ unsigned long long offset,
+ size_t size_to_read,
+ void* buffer,
+ size_t* out_size_read,
+ size_t error_string_max_size,
+ char* out_error_string,
+ void* user_data);
+
+/** Network access: close callback */
+typedef void (*proj_network_close_cbk_type)(PJ_CONTEXT* ctx,
+ PROJ_NETWORK_HANDLE* handle,
+ void* user_data);
+
+/** Network access: get HTTP headers */
+typedef const char* (*proj_network_get_header_value_cbk_type)(
+ PJ_CONTEXT* ctx,
+ PROJ_NETWORK_HANDLE* handle,
+ const char* header_name,
+ void* user_data);
+
+/** Network access: read range
+ *
+ * Read size_to_read bytes from handle, starting at offset, into
+ * buffer.
+ * During this read, the implementation should make sure to store the HTTP
+ * headers from the server response to be able to respond to
+ * proj_network_get_header_value_cbk_type callback.
+ *
+ * error_string_max_size should be the maximum size that can be written into
+ * the out_error_string buffer (including terminating nul character).
+ *
+ * @return the number of bytes actually read (0 in case of error)
+ */
+typedef size_t (*proj_network_read_range_type)(
+ PJ_CONTEXT* ctx,
+ PROJ_NETWORK_HANDLE* handle,
+ unsigned long long offset,
+ size_t size_to_read,
+ void* buffer,
+ size_t error_string_max_size,
+ char* out_error_string,
+ void* user_data);
+
+int PROJ_DLL proj_context_set_network_callbacks(
+ PJ_CONTEXT* ctx,
+ proj_network_open_cbk_type open_cbk,
+ proj_network_close_cbk_type close_cbk,
+ proj_network_get_header_value_cbk_type get_header_value_cbk,
+ proj_network_read_range_type read_range_cbk,
+ void* user_data);
+
+int PROJ_DLL proj_context_set_enable_network(PJ_CONTEXT* ctx,
+ int enabled);
+
+void PROJ_DLL proj_context_set_url_endpoint(PJ_CONTEXT* ctx, const char* url);
+
+void PROJ_DLL proj_grid_cache_set_enable(PJ_CONTEXT* ctx, int enabled);
+
+void PROJ_DLL proj_grid_cache_set_filename(PJ_CONTEXT* ctx, const char* fullname);
+
+void PROJ_DLL proj_grid_cache_set_max_size(PJ_CONTEXT* ctx, int max_size_MB);
+
+void PROJ_DLL proj_grid_cache_set_ttl(PJ_CONTEXT* ctx, int ttl_seconds);
+
+void PROJ_DLL proj_grid_cache_clear(PJ_CONTEXT* ctx);
+
+int PROJ_DLL proj_is_download_needed(PJ_CONTEXT* ctx,
+ const char* url_or_filename,
+ int ignore_ttl_setting);
+int PROJ_DLL proj_download_file(PJ_CONTEXT *ctx, const char *url_or_filename,
+ int ignore_ttl_setting,
+ int (*progress_cbk)(PJ_CONTEXT *, double pct,
+ void *user_data),
+ void *user_data);
+
+/*! @cond Doxygen_Suppress */
+
/* Manage the transformation definition object PJ */
PJ PROJ_DLL *proj_create (PJ_CONTEXT *ctx, const char *definition);
PJ PROJ_DLL *proj_create_argv (PJ_CONTEXT *ctx, int argc, char **argv);
@@ -624,6 +784,12 @@ typedef enum {
/** Ignore grid availability at all. Results will be presented as if
* all grids were available. */
PROJ_GRID_AVAILABILITY_IGNORED,
+
+ /** Results will be presented as if grids known to PROJ (that is
+ * registered in the grid_alternatives table of its database) were
+ * available. Used typically when networking is enabled.
+ */
+ PROJ_GRID_AVAILABILITY_KNOWN_AVAILABLE,
} PROJ_GRID_AVAILABILITY_USE;
/** \brief PROJ string version. */
diff --git a/src/proj_internal.h b/src/proj_internal.h
index 3e219682..a587c037 100644
--- a/src/proj_internal.h
+++ b/src/proj_internal.h
@@ -195,14 +195,6 @@ PJ_COORD pj_inv4d (PJ_COORD coo, PJ *P);
PJ_COORD PROJ_DLL pj_approx_2D_trans (PJ *P, PJ_DIRECTION direction, PJ_COORD coo);
PJ_COORD PROJ_DLL pj_approx_3D_trans (PJ *P, PJ_DIRECTION direction, PJ_COORD coo);
-
-/* Grid functionality */
-int proj_vgrid_init(PJ *P, const char *grids);
-int proj_hgrid_init(PJ *P, const char *grids);
-double proj_vgrid_value(PJ *P, PJ_LP lp, double vmultiplier);
-PJ_LP proj_hgrid_value(PJ *P, PJ_LP lp);
-PJ_LP proj_hgrid_apply(PJ *P, PJ_LP lp, PJ_DIRECTION direction);
-
void PROJ_DLL proj_log_error (PJ *P, const char *fmt, ...);
void proj_log_debug (PJ *P, const char *fmt, ...);
void proj_log_trace (PJ *P, const char *fmt, ...);
@@ -222,9 +214,6 @@ size_t pj_trim_argc (char *args);
char **pj_trim_argv (size_t argc, char *args);
char *pj_make_args (size_t argc, char **argv);
-/* Lowest level: Minimum support for fileapi */
-void proj_fileapi_set (PJ *P, void *fileapi);
-
typedef struct { double r, i; } COMPLEX;
/* Forward declarations and typedefs for stuff needed inside the PJ object */
@@ -354,6 +343,7 @@ struct PJconsts {
PJ_OPERATOR inv4d = nullptr;
PJ_DESTRUCTOR destructor = nullptr;
+ void (*reassign_context)(PJ*, projCtx_t *) = nullptr;
/*************************************************************************************
@@ -421,7 +411,6 @@ struct PJconsts {
int geoc = 0; /* Geocentric latitude flag */
int is_latlong = 0; /* proj=latlong ... not really a projection at all */
int is_geocent = 0; /* proj=geocent ... not really a projection at all */
- int is_pipeline = 0; /* 1 if PJ represents a pipeline */
int need_ellps = 0; /* 0 for operations that are purely cartesian */
int skip_fwd_prepare = 0;
int skip_fwd_finalize = 0;
@@ -478,32 +467,16 @@ struct PJconsts {
int datum_type = PJD_UNKNOWN; /* PJD_UNKNOWN/3PARAM/7PARAM/GRIDSHIFT/WGS84 */
double datum_params[7] = {0,0,0,0,0,0,0}; /* Parameters for 3PARAM and 7PARAM */
- struct _pj_gi **gridlist = nullptr; /* TODO: Description needed */
- int gridlist_count = 0;
- int has_geoid_vgrids = 0; /* TODO: Description needed */
- struct _pj_gi **vgridlist_geoid = nullptr; /* TODO: Description needed */
- int vgridlist_geoid_count = 0;
+ int has_geoid_vgrids = 0; /* used by legacy transform.cpp */
+ void* hgrids_legacy = nullptr; /* used by legacy transform.cpp. Is a pointer to a ListOfHGrids* */
+ void* vgrids_legacy = nullptr; /* used by legacy transform.cpp. Is a pointer to a ListOfVGrids* */
double from_greenwich = 0.0; /* prime meridian offset (in radians) */
double long_wrap_center = 0.0; /* 0.0 for -180 to 180, actually in radians*/
int is_long_wrap_set = 0;
char axis[4] = {0,0,0,0}; /* Axis order, pj_transform/pj_adjust_axis */
- /* New Datum Shift Grid Catalogs */
- char *catalog_name = nullptr;
- struct _PJ_GridCatalog *catalog = nullptr;
-
- double datum_date = 0.0; /* TODO: Description needed */
-
- struct _pj_gi *last_before_grid = nullptr; /* TODO: Description needed */
- PJ_Region last_before_region = {0,0,0,0}; /* TODO: Description needed */
- double last_before_date = 0.0; /* TODO: Description needed */
-
- struct _pj_gi *last_after_grid = nullptr; /* TODO: Description needed */
- PJ_Region last_after_region = {0,0,0,0}; /* TODO: Description needed */
- double last_after_date = 0.0; /* TODO: Description needed */
-
/*************************************************************************************
ISO-19111 interface
**************************************************************************************/
@@ -685,24 +658,63 @@ struct FACTORS {
#define PJD_ERR_INCONSISTENT_UNIT -59
#define PJD_ERR_MUTUALLY_EXCLUSIVE_ARGS -60
#define PJD_ERR_GENERIC_ERROR -61
+#define PJD_ERR_NETWORK_ERROR -62
/* NOTE: Remember to update src/strerrno.cpp, src/apps/gie.cpp and transient_error in */
/* src/transform.cpp when adding new value */
+// Legacy
struct projFileAPI_t;
struct projCppContext;
+struct projNetworkCallbacksAndData
+{
+ bool enabled = false;
+ bool enabled_env_variable_checked = false; // whereas we have checked PROJ_NETWORK env variable
+ proj_network_open_cbk_type open = nullptr;
+ proj_network_close_cbk_type close = nullptr;
+ proj_network_get_header_value_cbk_type get_header_value = nullptr;
+ proj_network_read_range_type read_range = nullptr;
+ void* user_data = nullptr;
+};
+
+struct projGridChunkCache
+{
+ bool enabled = true;
+ std::string filename{};
+ long long max_size = 300 * 1024 * 1024;
+ int ttl = 86400; // 1 day
+};
+
+struct projFileApiCallbackAndData
+{
+ PROJ_FILE_HANDLE* (*open_cbk)(PJ_CONTEXT *ctx, const char *filename, PROJ_OPEN_ACCESS access, void* user_data) = nullptr;
+ size_t (*read_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE*, void* buffer, size_t size, void* user_data) = nullptr;
+ size_t (*write_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE*, const void* buffer, size_t size, void* user_data) = nullptr;
+ int (*seek_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE*, long long offset, int whence, void* user_data) = nullptr;
+ unsigned long long (*tell_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE*, void* user_data) = nullptr;
+ void (*close_cbk)(PJ_CONTEXT *ctx, PROJ_FILE_HANDLE*, void* user_data) = nullptr;
+
+ int (*exists_cbk)(PJ_CONTEXT *ctx, const char *filename, void* user_data) = nullptr;
+ int (*mkdir_cbk)(PJ_CONTEXT *ctx, const char *filename, void* user_data) = nullptr;
+ int (*unlink_cbk)(PJ_CONTEXT *ctx, const char *filename, void* user_data) = nullptr;
+ int (*rename_cbk)(PJ_CONTEXT *ctx, const char *oldPath, const char *newPath, void* user_data) = nullptr;
+
+ void* user_data = nullptr;
+};
+
/* proj thread context */
struct projCtx_t {
int last_errno = 0;
int debug_level = 0;
void (*logger)(void *, int, const char *) = nullptr;
void *logger_app_data = nullptr;
- struct projFileAPI_t *fileapi = nullptr;
+ struct projFileAPI_t *fileapi_legacy = nullptr; // for proj_api.h legacy API
struct projCppContext* cpp_context = nullptr; /* internal context for C++ code */
int use_proj4_init_rules = -1; /* -1 = unknown, 0 = no, 1 = yes */
int epsg_file_exists = -1; /* -1 = unknown, 0 = no, 1 = yes */
+ std::string env_var_proj_lib{}; // content of PROJ_LIB environment variable. Use Filemanager::getProjLibEnvVar() to access
std::vector<std::string> search_paths{};
const char **c_compat_paths = nullptr; // same, but for projinfo usage
@@ -710,6 +722,18 @@ struct projCtx_t {
const char* (*file_finder) (PJ_CONTEXT *, const char*, void* user_data) = nullptr;
void* file_finder_user_data = nullptr;
+ projNetworkCallbacksAndData networking{};
+ bool defer_grid_opening = false; // set by pj_obj_create()
+
+ projFileApiCallbackAndData fileApi{};
+ std::string custom_sqlite3_vfs_name{};
+
+ bool iniFileLoaded = false;
+ std::string endpoint{};
+
+ std::string user_writable_directory{};
+ projGridChunkCache gridChunkCache{};
+
int projStringParserCreateFromPROJStringRecursionCounter = 0; // to avoid potential infinite recursion in PROJStringParser::createFromPROJString()
projCtx_t() = default;
@@ -766,56 +790,6 @@ PJ *pj_projection_specific_setup_##name (PJ *P)
#endif /* def PJ_LIB__ */
-
-#define MAX_TAB_ID 80
-typedef struct { float lam, phi; } FLP;
-typedef struct { pj_int32 lam, phi; } ILP;
-
-struct CTABLE {
- char id[MAX_TAB_ID]; /* ascii info */
- PJ_LP ll; /* lower left corner coordinates */
- PJ_LP del; /* size of cells */
- ILP lim; /* limits of conversion matrix */
- FLP *cvs; /* conversion matrix */
-};
-
-typedef struct _pj_gi {
- char *gridname; /* identifying name of grid, eg "conus" or ntv2_0.gsb */
- char *filename; /* full path to filename */
-
- const char *format; /* format of this grid, ie "ctable", "ntv1",
- "ntv2" or "missing". */
-
- long grid_offset; /* offset in file, for delayed loading */
- int must_swap; /* only for NTv2 */
-
- struct CTABLE *ct;
-
- struct _pj_gi *next;
- struct _pj_gi *child;
-} PJ_GRIDINFO;
-
-typedef struct {
- PJ_Region region;
- int priority; /* higher used before lower */
- double date; /* year.fraction */
- char *definition; /* usually the gridname */
-
- PJ_GRIDINFO *gridinfo;
- int available; /* 0=unknown, 1=true, -1=false */
-} PJ_GridCatalogEntry;
-
-typedef struct _PJ_GridCatalog {
- char *catalog_name;
-
- PJ_Region region; /* maximum extent of catalog data */
-
- int entry_count;
- PJ_GridCatalogEntry *entries;
-
- struct _PJ_GridCatalog *next;
-} PJ_GridCatalog;
-
/* procedure prototypes */
double PROJ_DLL dmstor(const char *, char **);
double dmstor_ctx(projCtx_t *ctx, const char *, char **);
@@ -862,52 +836,6 @@ COMPLEX pj_zpolyd1(COMPLEX, const COMPLEX *, int, COMPLEX *);
int pj_deriv(PJ_LP, double, const PJ *, struct DERIVS *);
int pj_factors(PJ_LP, const PJ *, double, struct FACTORS *);
-/* nadcon related protos */
-struct CTABLE* find_ctable(projCtx_t *ctx, PJ_LP input, int grid_count, PJ_GRIDINFO **tables);
-
-PJ_LP nad_intr(PJ_LP, struct CTABLE *);
-PJ_LP nad_cvt(projCtx_t *ctx, PJ_LP in, int inverse, struct CTABLE *ct, int grid_count, PJ_GRIDINFO **tables);
-struct CTABLE *nad_init(projCtx_t *ctx, char *);
-struct CTABLE *nad_ctable_init( projCtx_t *ctx, struct projFileAPI_t* fid );
-int nad_ctable_load( projCtx_t *ctx, struct CTABLE *, struct projFileAPI_t* fid );
-struct CTABLE *nad_ctable2_init( projCtx_t *ctx, struct projFileAPI_t* fid );
-int nad_ctable2_load( projCtx_t *ctx, struct CTABLE *, struct projFileAPI_t* fid );
-void nad_free(struct CTABLE *);
-
-/* higher level handling of datum grid shift files */
-
-int pj_apply_vgridshift( PJ *defn, const char *listname,
- PJ_GRIDINFO ***gridlist_p,
- int *gridlist_count_p,
- int inverse,
- long point_count, int point_offset,
- double *x, double *y, double *z );
-int pj_apply_gridshift_2( PJ *defn, int inverse,
- long point_count, int point_offset,
- double *x, double *y, double *z );
-int pj_apply_gridshift_3( projCtx_t *ctx,
- PJ_GRIDINFO **gridlist, int gridlist_count,
- int inverse, long point_count, int point_offset,
- double *x, double *y, double *z );
-
-PJ_GRIDINFO **pj_gridlist_from_nadgrids( projCtx_t *, const char *, int * );
-
-PJ_GRIDINFO *pj_gridinfo_init( projCtx_t *, const char * );
-int pj_gridinfo_load( projCtx_t *, PJ_GRIDINFO * );
-void pj_gridinfo_free( projCtx_t *, PJ_GRIDINFO * );
-
-PJ_GridCatalog *pj_gc_findcatalog( projCtx_t *, const char * );
-PJ_GridCatalog *pj_gc_readcatalog( projCtx_t *, const char * );
-void pj_gc_unloadall( projCtx_t *);
-int pj_gc_apply_gridshift( PJ *defn, int inverse,
- long point_count, int point_offset,
- double *x, double *y, double *z );
-int pj_gc_apply_gridshift( PJ *defn, int inverse,
- long point_count, int point_offset,
- double *x, double *y, double *z );
-
-double pj_gc_parsedate( projCtx_t *, const char * );
-
void *proj_mdist_ini(double);
double proj_mdist(double, double, double, const void *);
double proj_inv_mdist(projCtx_t *ctx, double, const void *);
@@ -932,7 +860,16 @@ std::string pj_double_quote_string_param_if_needed(const std::string& str);
PJ *pj_create_internal (PJ_CONTEXT *ctx, const char *definition);
PJ *pj_create_argv_internal (PJ_CONTEXT *ctx, int argc, char **argv);
-void pj_pipeline_assign_context_to_steps( PJ* P, PJ_CONTEXT* ctx );
+// For use by projinfo
+bool PROJ_DLL pj_context_is_network_enabled(PJ_CONTEXT* ctx);
+
+std::string pj_context_get_url_endpoint(PJ_CONTEXT* ctx);
+
+void pj_load_ini(PJ_CONTEXT* ctx);
+
+// Exported for testing purposes only
+std::string PROJ_DLL pj_context_get_grid_cache_filename(PJ_CONTEXT *ctx);
+std::string PROJ_DLL pj_context_get_user_writable_directory(PJ_CONTEXT *ctx, bool create);
/* classic public API */
#include "proj_api.h"
diff --git a/src/sqlite3_utils.cpp b/src/sqlite3_utils.cpp
new file mode 100644
index 00000000..673eb89c
--- /dev/null
+++ b/src/sqlite3_utils.cpp
@@ -0,0 +1,194 @@
+/******************************************************************************
+ * Project: PROJ
+ * Purpose: SQLite3 related utilities
+ * Author: Even Rouault, <even.rouault at spatialys.com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2019, Even Rouault, <even.rouault at spatialys.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *****************************************************************************/
+
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Weffc++"
+#endif
+
+#include "sqlite3_utils.hpp"
+
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
+#include <cstdlib>
+#include <cstring>
+#include <sstream> // std::ostringstream
+
+NS_PROJ_START
+
+// ---------------------------------------------------------------------------
+
+SQLite3VFS::SQLite3VFS(sqlite3_vfs *vfs) : vfs_(vfs) {}
+
+// ---------------------------------------------------------------------------
+
+SQLite3VFS::~SQLite3VFS() {
+ if (vfs_) {
+ sqlite3_vfs_unregister(vfs_);
+ delete vfs_;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+struct pj_sqlite3_vfs : public sqlite3_vfs {
+ std::string namePtr{};
+ bool fakeSync = false;
+ bool fakeLock = false;
+};
+
+// ---------------------------------------------------------------------------
+
+const char *SQLite3VFS::name() const {
+ return static_cast<pj_sqlite3_vfs *>(vfs_)->namePtr.c_str();
+}
+
+// ---------------------------------------------------------------------------
+
+typedef int (*ClosePtr)(sqlite3_file *);
+
+// ---------------------------------------------------------------------------
+
+static int VFSClose(sqlite3_file *file) {
+ sqlite3_vfs *defaultVFS = sqlite3_vfs_find(nullptr);
+ assert(defaultVFS);
+ ClosePtr defaultClosePtr;
+ std::memcpy(&defaultClosePtr,
+ reinterpret_cast<char *>(file) + defaultVFS->szOsFile,
+ sizeof(ClosePtr));
+ void *methods = const_cast<sqlite3_io_methods *>(file->pMethods);
+ int ret = defaultClosePtr(file);
+ std::free(methods);
+ return ret;
+}
+
+// ---------------------------------------------------------------------------
+
+static int VSFNoOpLockUnlockSync(sqlite3_file *, int) { return SQLITE_OK; }
+
+// ---------------------------------------------------------------------------
+
+static int VFSCustomOpen(sqlite3_vfs *vfs, const char *name, sqlite3_file *file,
+ int flags, int *outFlags) {
+ auto realVFS = static_cast<pj_sqlite3_vfs *>(vfs);
+ sqlite3_vfs *defaultVFS = static_cast<sqlite3_vfs *>(vfs->pAppData);
+ int ret = defaultVFS->xOpen(defaultVFS, name, file, flags, outFlags);
+ if (ret == SQLITE_OK) {
+ ClosePtr defaultClosePtr = file->pMethods->xClose;
+ assert(defaultClosePtr);
+ sqlite3_io_methods *methods = static_cast<sqlite3_io_methods *>(
+ std::malloc(sizeof(sqlite3_io_methods)));
+ if (!methods) {
+ file->pMethods->xClose(file);
+ return SQLITE_NOMEM;
+ }
+ memcpy(methods, file->pMethods, sizeof(sqlite3_io_methods));
+ methods->xClose = VFSClose;
+ if (realVFS->fakeSync) {
+ // Disable xSync because it can be significantly slow and we don't
+ // need
+ // that level of data integrity garanty for the cache.
+ methods->xSync = VSFNoOpLockUnlockSync;
+ }
+ if (realVFS->fakeLock) {
+ methods->xLock = VSFNoOpLockUnlockSync;
+ methods->xUnlock = VSFNoOpLockUnlockSync;
+ }
+ file->pMethods = methods;
+ // Save original xClose pointer at end of file structure
+ std::memcpy(reinterpret_cast<char *>(file) + defaultVFS->szOsFile,
+ &defaultClosePtr, sizeof(ClosePtr));
+ }
+ return ret;
+}
+
+// ---------------------------------------------------------------------------
+
+static int VFSCustomAccess(sqlite3_vfs *vfs, const char *zName, int flags,
+ int *pResOut) {
+ sqlite3_vfs *defaultVFS = static_cast<sqlite3_vfs *>(vfs->pAppData);
+ // Do not bother stat'ing for journal or wal files
+ if (std::strstr(zName, "-journal") || std::strstr(zName, "-wal")) {
+ *pResOut = false;
+ return SQLITE_OK;
+ }
+ return defaultVFS->xAccess(defaultVFS, zName, flags, pResOut);
+}
+
+// ---------------------------------------------------------------------------
+
+std::unique_ptr<SQLite3VFS> SQLite3VFS::create(bool fakeSync, bool fakeLock,
+ bool skipStatJournalAndWAL) {
+ sqlite3_vfs *defaultVFS = sqlite3_vfs_find(nullptr);
+ assert(defaultVFS);
+
+ auto vfs = new pj_sqlite3_vfs();
+ vfs->fakeSync = fakeSync;
+ vfs->fakeLock = fakeLock;
+
+ auto vfsUnique = std::unique_ptr<SQLite3VFS>(new SQLite3VFS(vfs));
+
+ std::ostringstream buffer;
+ buffer << vfs;
+ vfs->namePtr = buffer.str();
+
+ vfs->iVersion = 1;
+ vfs->szOsFile = defaultVFS->szOsFile + sizeof(ClosePtr);
+ vfs->mxPathname = defaultVFS->mxPathname;
+ vfs->zName = vfs->namePtr.c_str();
+ vfs->pAppData = defaultVFS;
+ vfs->xOpen = VFSCustomOpen;
+ vfs->xDelete = defaultVFS->xDelete;
+ vfs->xAccess =
+ skipStatJournalAndWAL ? VFSCustomAccess : defaultVFS->xAccess;
+ vfs->xFullPathname = defaultVFS->xFullPathname;
+ vfs->xDlOpen = defaultVFS->xDlOpen;
+ vfs->xDlError = defaultVFS->xDlError;
+ vfs->xDlSym = defaultVFS->xDlSym;
+ vfs->xDlClose = defaultVFS->xDlClose;
+ vfs->xRandomness = defaultVFS->xRandomness;
+ vfs->xSleep = defaultVFS->xSleep;
+ vfs->xCurrentTime = defaultVFS->xCurrentTime;
+ vfs->xGetLastError = defaultVFS->xGetLastError;
+ vfs->xCurrentTimeInt64 = defaultVFS->xCurrentTimeInt64;
+ if (sqlite3_vfs_register(vfs, false) == SQLITE_OK) {
+ return vfsUnique;
+ }
+ delete vfsUnique->vfs_;
+ vfsUnique->vfs_ = nullptr;
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+SQLiteStatement::SQLiteStatement(sqlite3_stmt *hStmtIn) : hStmt(hStmtIn) {}
+
+// ---------------------------------------------------------------------------
+
+NS_PROJ_END
diff --git a/src/sqlite3_utils.hpp b/src/sqlite3_utils.hpp
new file mode 100644
index 00000000..ef141d1f
--- /dev/null
+++ b/src/sqlite3_utils.hpp
@@ -0,0 +1,127 @@
+/******************************************************************************
+ * Project: PROJ
+ * Purpose: SQLite3 related utilities
+ * Author: Even Rouault, <even.rouault at spatialys.com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2019, Even Rouault, <even.rouault at spatialys.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *****************************************************************************/
+
+#ifndef SQLITE3_HPP_INCLUDED
+#define SQLITE3_HPP_INCLUDED
+
+#include <memory>
+
+#include <sqlite3.h>
+
+#include "proj.h"
+#include "proj/util.hpp"
+
+NS_PROJ_START
+
+//! @cond Doxygen_Suppress
+
+class SQLite3VFS {
+ sqlite3_vfs *vfs_ = nullptr;
+
+ explicit SQLite3VFS(sqlite3_vfs *vfs);
+
+ SQLite3VFS(const SQLite3VFS &) = delete;
+ SQLite3VFS &operator=(const SQLite3VFS &) = delete;
+
+ public:
+ ~SQLite3VFS();
+
+ static std::unique_ptr<SQLite3VFS> create(bool fakeSync, bool fakeLock,
+ bool skipStatJournalAndWAL);
+ const char *name() const;
+ sqlite3_vfs *raw() { return vfs_; }
+};
+
+// ---------------------------------------------------------------------------
+
+class SQLiteStatement {
+ sqlite3_stmt *hStmt = nullptr;
+ int iBindIdx = 1;
+ int iResIdx = 0;
+ SQLiteStatement(const SQLiteStatement &) = delete;
+ SQLiteStatement &operator=(const SQLiteStatement &) = delete;
+
+ public:
+ explicit SQLiteStatement(sqlite3_stmt *hStmtIn);
+ ~SQLiteStatement() { sqlite3_finalize(hStmt); }
+
+ int execute() { return sqlite3_step(hStmt); }
+
+ void bindNull() {
+ sqlite3_bind_null(hStmt, iBindIdx);
+ iBindIdx++;
+ }
+
+ void bindText(const char *txt) {
+ sqlite3_bind_text(hStmt, iBindIdx, txt, -1, nullptr);
+ iBindIdx++;
+ }
+
+ void bindInt64(sqlite3_int64 v) {
+ sqlite3_bind_int64(hStmt, iBindIdx, v);
+ iBindIdx++;
+ }
+
+ void bindBlob(const void *blob, size_t blob_size) {
+ sqlite3_bind_blob(hStmt, iBindIdx, blob, static_cast<int>(blob_size),
+ nullptr);
+ iBindIdx++;
+ }
+
+ const char *getText() {
+ auto ret = sqlite3_column_text(hStmt, iResIdx);
+ iResIdx++;
+ return reinterpret_cast<const char *>(ret);
+ }
+
+ sqlite3_int64 getInt64() {
+ auto ret = sqlite3_column_int64(hStmt, iResIdx);
+ iResIdx++;
+ return ret;
+ }
+
+ const void *getBlob(int &size) {
+ size = sqlite3_column_bytes(hStmt, iResIdx);
+ auto ret = sqlite3_column_blob(hStmt, iResIdx);
+ iResIdx++;
+ return ret;
+ }
+
+ void reset() {
+ sqlite3_reset(hStmt);
+ iBindIdx = 1;
+ iResIdx = 0;
+ }
+
+ void resetResIndex() { iResIdx = 0; }
+};
+
+//! @endcond Doxygen_Suppress
+
+NS_PROJ_END
+
+#endif // SQLITE3_HPP_INCLUDED
diff --git a/src/strerrno.cpp b/src/strerrno.cpp
index 9bf5f45a..5ae0d7e1 100644
--- a/src/strerrno.cpp
+++ b/src/strerrno.cpp
@@ -71,6 +71,7 @@ pj_err_list[] = {
"inconsistent unit type between input and output", /* -59 */
"arguments are mutually exclusive", /* -60 */
"generic error of unknown origin", /* -61 */
+ "network error", /* -62 */
/* When adding error messages, remember to update ID defines in
projects.h, and transient_error array in pj_transform */
diff --git a/src/transform.cpp b/src/transform.cpp
index 781c0061..ea3d9ae2 100644
--- a/src/transform.cpp
+++ b/src/transform.cpp
@@ -34,6 +34,9 @@
#include "proj.h"
#include "proj_internal.h"
#include "geocent.h"
+#include "grids.hpp"
+
+using namespace NS_PROJ;
static int adjust_axis( projCtx ctx, const char *axis, int denormalize_flag,
long point_count, int point_offset,
@@ -447,6 +450,78 @@ static int height_unit (PJ *P, PJ_DIRECTION dir, long n, int dist, double *z) {
}
+/************************************************************************/
+/* pj_apply_vgridshift() */
+/* */
+/* This implementation takes uses the gridlist from a coordinate */
+/* system definition. If the gridlist has not yet been */
+/* populated in the coordinate system definition we set it up */
+/* now. */
+/************************************************************************/
+static int pj_apply_vgridshift( PJ *defn,
+ int inverse,
+ long point_count, int point_offset,
+ double *x, double *y, double *z )
+
+{
+ if( defn->vgrids_legacy == nullptr )
+ {
+ defn->vgrids_legacy = new ListOfVGrids;
+ auto vgrids = pj_vgrid_init(defn, "geoidgrids");
+ if( vgrids.empty() )
+ return 0;
+ *static_cast<ListOfVGrids*>(defn->vgrids_legacy) = std::move(vgrids);
+ }
+ if( static_cast<ListOfVGrids*>(defn->vgrids_legacy)->empty() )
+ {
+ return 0;
+ }
+
+ for( int i = 0; i < point_count; i++ )
+ {
+ double value;
+ long io = i * point_offset;
+ PJ_LP input;
+
+ input.phi = y[io];
+ input.lam = x[io];
+
+ value = pj_vgrid_value(defn, *static_cast<ListOfVGrids*>(defn->vgrids_legacy), input, 1.0);
+
+ if( inverse )
+ z[io] -= value;
+ else
+ z[io] += value;
+
+ if( value == HUGE_VAL )
+ {
+ std::string gridlist;
+
+ proj_log_debug(defn,
+ "pj_apply_vgridshift(): failed to find a grid shift table for\n"
+ " location (%.7fdW,%.7fdN)",
+ x[io] * RAD_TO_DEG,
+ y[io] * RAD_TO_DEG );
+
+ for( const auto& gridset: *static_cast<ListOfVGrids*>(defn->vgrids_legacy) )
+ {
+ if( gridlist.empty() )
+ gridlist += " tried: ";
+ else
+ gridlist += ',';
+ gridlist += gridset->name();
+ }
+
+ proj_log_debug(defn, "%s", gridlist.c_str());
+ pj_ctx_set_errno( defn->ctx, PJD_ERR_GRID_AREA );
+
+ return PJD_ERR_GRID_AREA;
+ }
+ }
+
+ return 0;
+}
+
/* -------------------------------------------------------------------- */
/* Transform to ellipsoidal heights if needed */
@@ -457,10 +532,7 @@ static int geometric_to_orthometric (PJ *P, PJ_DIRECTION dir, long n, int dist,
return 0;
if (z==nullptr)
return PJD_ERR_GEOCENTRIC;
- err = pj_apply_vgridshift (P, "sgeoidgrids",
- &(P->vgridlist_geoid),
- &(P->vgridlist_geoid_count),
- dir==PJ_FWD ? 1 : 0, n, dist, x, y, z );
+ err = pj_apply_vgridshift (P, dir==PJ_FWD ? 1 : 0, n, dist, x, y, z );
if (err)
return pj_ctx_get_errno(P->ctx);
return 0;
@@ -822,6 +894,66 @@ int pj_geocentric_from_wgs84( PJ *defn,
return 0;
}
+
+/************************************************************************/
+/* pj_apply_gridshift_2() */
+/* */
+/* This implementation uses the gridlist from a coordinate */
+/* system definition. If the gridlist has not yet been */
+/* populated in the coordinate system definition we set it up */
+/* now. */
+/************************************************************************/
+static
+int pj_apply_gridshift_2( PJ *defn, int inverse,
+ long point_count, int point_offset,
+ double *x, double *y, double * /*z*/ )
+
+{
+ if( defn->hgrids_legacy == nullptr )
+ {
+ defn->hgrids_legacy = new ListOfHGrids;
+ auto hgrids = pj_hgrid_init(defn, "nadgrids");
+ if( hgrids.empty() )
+ return 0;
+ *static_cast<ListOfHGrids*>(defn->hgrids_legacy) = std::move(hgrids);
+ }
+ if( static_cast<ListOfHGrids*>(defn->hgrids_legacy)->empty() )
+ {
+ return 0;
+ }
+
+ for( long i = 0; i < point_count; i++ )
+ {
+ PJ_LP input;
+
+ long io = i * point_offset;
+ input.phi = y[io];
+ input.lam = x[io];
+
+ auto output = pj_hgrid_apply(defn->ctx, *static_cast<ListOfHGrids*>(defn->hgrids_legacy), input, inverse ? PJ_INV : PJ_FWD);
+
+ if ( output.lam != HUGE_VAL )
+ {
+ y[io] = output.phi;
+ x[io] = output.lam;
+ }
+ else
+ {
+ if( defn->ctx->debug_level >= PJ_LOG_DEBUG_MAJOR )
+ {
+ pj_log( defn->ctx, PJ_LOG_DEBUG_MAJOR,
+ "pj_apply_gridshift(): failed to find a grid shift table for\n"
+ " location (%.7fdW,%.7fdN)",
+ x[io] * RAD_TO_DEG,
+ y[io] * RAD_TO_DEG );
+ }
+ }
+ }
+
+ return 0;
+}
+
+
/************************************************************************/
/* pj_datum_transform() */
/* */
@@ -1062,3 +1194,8 @@ static int adjust_axis( projCtx ctx,
return 0;
}
+// ---------------------------------------------------------------------------
+
+void pj_deallocate_grids()
+{
+}
diff --git a/src/transformations/deformation.cpp b/src/transformations/deformation.cpp
index f1311a54..8aee50c9 100644
--- a/src/transformations/deformation.cpp
+++ b/src/transformations/deformation.cpp
@@ -56,20 +56,90 @@ grid-values in units of mm/year in ENU-space.
#include "proj.h"
#include "proj_internal.h"
#include <math.h>
+#include "grids.hpp"
+
+#include <algorithm>
PROJ_HEAD(deformation, "Kinematic grid shift");
#define TOL 1e-8
#define MAX_ITERATIONS 10
+using namespace NS_PROJ;
+
namespace { // anonymous namespace
-struct pj_opaque {
- double dt;
- double t_epoch;
- PJ *cart;
+struct deformationData {
+ double dt = 0;
+ double t_epoch = 0;
+ PJ *cart = nullptr;
+ ListOfGenericGrids grids{};
+ ListOfHGrids hgrids{};
+ ListOfVGrids vgrids{};
};
} // anonymous namespace
+// ---------------------------------------------------------------------------
+
+static bool get_grid_values(PJ* P,
+ deformationData* Q,
+ const PJ_LP& lp,
+ double& vx,
+ double& vy,
+ double& vz)
+{
+ GenericShiftGridSet* gridset = nullptr;
+ auto grid = pj_find_generic_grid(Q->grids, lp, gridset);
+ if( !grid ) {
+ return false;
+ }
+ if( grid->isNullGrid() ) {
+ vx = 0;
+ vy = 0;
+ vz = 0;
+ return true;
+ }
+ const auto samplesPerPixel = grid->samplesPerPixel();
+ if( samplesPerPixel < 3 ) {
+ proj_log_error(P, "deformation: grid has not enough samples");
+ return false;
+ }
+ int sampleE = 0;
+ int sampleN = 1;
+ int sampleU = 2;
+ for( int i = 0; i < samplesPerPixel; i++ )
+ {
+ const auto desc = grid->description(i);
+ if( desc == "east_velocity") {
+ sampleE = i;
+ } else if( desc == "north_velocity") {
+ sampleN = i;
+ } else if( desc == "up_velocity") {
+ sampleU = i;
+ }
+ }
+ const auto unit = grid->unit(sampleE);
+ if( !unit.empty() && unit != "millimetres per year" ) {
+ proj_log_error(P, "deformation: Only unit=millimetres per year currently handled");
+ return false;
+ }
+
+ bool must_retry = false;
+ if( !pj_bilinear_interpolation_three_samples(grid, lp,
+ sampleE, sampleN, sampleU,
+ vx, vy, vz,
+ must_retry) )
+ {
+ if( must_retry )
+ return get_grid_values( P, Q, lp, vx, vy, vz);
+ return false;
+ }
+ // divide by 1000 to get m/year
+ vx /= 1000;
+ vy /= 1000;
+ vz /= 1000;
+ return true;
+}
+
/********************************************************************************/
static PJ_XYZ get_grid_shift(PJ* P, const PJ_XYZ& cartesian) {
/********************************************************************************
@@ -85,22 +155,39 @@ static PJ_XYZ get_grid_shift(PJ* P, const PJ_XYZ& cartesian) {
PJ_COORD geodetic, shift, temp;
double sp, cp, sl, cl;
int previous_errno = proj_errno_reset(P);
+ auto Q = static_cast<deformationData*>(P->opaque);
/* cartesian to geodetic */
- geodetic.lpz = pj_inv3d(cartesian, static_cast<struct pj_opaque*>(P->opaque)->cart);
+ geodetic.lpz = pj_inv3d(cartesian, Q->cart);
/* look up correction values in grids */
- shift.lp = proj_hgrid_value(P, geodetic.lp);
- shift.enu.u = proj_vgrid_value(P, geodetic.lp, 1.0);
-
- if (proj_errno(P) == PJD_ERR_GRID_AREA)
- proj_log_debug(P, "deformation: coordinate (%.3f, %.3f) outside deformation model",
- proj_todeg(geodetic.lpz.lam), proj_todeg(geodetic.lpz.phi));
-
- /* grid values are stored as mm/yr, we need m/yr */
- shift.xyz.x /= 1000;
- shift.xyz.y /= 1000;
- shift.xyz.z /= 1000;
+ if( !Q->grids.empty() )
+ {
+ double vx = 0;
+ double vy = 0;
+ double vz = 0;
+ if( !get_grid_values(P, Q, geodetic.lp, vx, vy, vz) )
+ {
+ return proj_coord_error().xyz;
+ }
+ shift.xyz.x = vx;
+ shift.xyz.y = vy;
+ shift.xyz.z = vz;
+ }
+ else
+ {
+ shift.lp = pj_hgrid_value(P, Q->hgrids, geodetic.lp);
+ shift.enu.u = pj_vgrid_value(P, Q->vgrids, geodetic.lp, 1.0);
+
+ if (proj_errno(P) == PJD_ERR_GRID_AREA)
+ proj_log_debug(P, "deformation: coordinate (%.3f, %.3f) outside deformation model",
+ proj_todeg(geodetic.lpz.lam), proj_todeg(geodetic.lpz.phi));
+
+ /* grid values are stored as mm/yr, we need m/yr */
+ shift.xyz.x /= 1000;
+ shift.xyz.y /= 1000;
+ shift.xyz.z /= 1000;
+ }
/* pre-calc cosines and sines */
sp = sin(geodetic.lpz.phi);
@@ -130,6 +217,9 @@ static PJ_XYZ reverse_shift(PJ *P, PJ_XYZ input, double dt) {
int i = MAX_ITERATIONS;
delta = get_grid_shift(P, input);
+ if (delta.x == HUGE_VAL) {
+ return delta;
+ }
/* Store the origial z shift for later application */
z0 = delta.z;
@@ -163,7 +253,7 @@ static PJ_XYZ reverse_shift(PJ *P, PJ_XYZ input, double dt) {
}
static PJ_XYZ forward_3d(PJ_LPZ lpz, PJ *P) {
- struct pj_opaque *Q = (struct pj_opaque *) P->opaque;
+ struct deformationData *Q = (struct deformationData *) P->opaque;
PJ_COORD out, in;
PJ_XYZ shift;
in.lpz = lpz;
@@ -176,6 +266,9 @@ static PJ_XYZ forward_3d(PJ_LPZ lpz, PJ *P) {
}
shift = get_grid_shift(P, in.xyz);
+ if (shift.x == HUGE_VAL) {
+ return shift;
+ }
out.xyz.x += Q->dt * shift.x;
out.xyz.y += Q->dt * shift.y;
@@ -186,7 +279,7 @@ static PJ_XYZ forward_3d(PJ_LPZ lpz, PJ *P) {
static PJ_COORD forward_4d(PJ_COORD in, PJ *P) {
- struct pj_opaque *Q = (struct pj_opaque *) P->opaque;
+ struct deformationData *Q = (struct deformationData *) P->opaque;
double dt;
PJ_XYZ shift;
PJ_COORD out = in;
@@ -209,7 +302,7 @@ static PJ_COORD forward_4d(PJ_COORD in, PJ *P) {
static PJ_LPZ reverse_3d(PJ_XYZ in, PJ *P) {
- struct pj_opaque *Q = (struct pj_opaque *) P->opaque;
+ struct deformationData *Q = (struct deformationData *) P->opaque;
PJ_COORD out;
out.xyz = in;
@@ -225,7 +318,7 @@ static PJ_LPZ reverse_3d(PJ_XYZ in, PJ *P) {
}
static PJ_COORD reverse_4d(PJ_COORD in, PJ *P) {
- struct pj_opaque *Q = (struct pj_opaque *) P->opaque;
+ struct deformationData *Q = (struct deformationData *) P->opaque;
PJ_COORD out = in;
double dt;
@@ -244,23 +337,23 @@ static PJ *destructor(PJ *P, int errlev) {
if (nullptr==P)
return nullptr;
- if (nullptr==P->opaque)
- return pj_default_destructor (P, errlev);
-
- if (static_cast<struct pj_opaque*>(P->opaque)->cart)
- static_cast<struct pj_opaque*>(P->opaque)->cart->destructor (static_cast<struct pj_opaque*>(P->opaque)->cart, errlev);
+ auto Q = static_cast<struct deformationData*>(P->opaque);
+ if( Q )
+ {
+ if (Q->cart)
+ Q->cart->destructor (Q->cart, errlev);
+ delete Q;
+ }
+ P->opaque = nullptr;
return pj_default_destructor(P, errlev);
}
PJ *TRANSFORMATION(deformation,1) {
- int has_xy_grids = 0;
- int has_z_grids = 0;
- struct pj_opaque *Q = static_cast<struct pj_opaque*>(pj_calloc (1, sizeof (struct pj_opaque)));
- if (nullptr==Q)
- return destructor(P, ENOMEM);
+ auto Q = new deformationData;
P->opaque = (void *) Q;
+ P->destructor = destructor;
// Pass a dummy ellipsoid definition that will be overridden just afterwards
Q->cart = proj_create(P->ctx, "+proj=cart +a=1");
@@ -270,25 +363,38 @@ PJ *TRANSFORMATION(deformation,1) {
/* inherit ellipsoid definition from P to Q->cart */
pj_inherit_ellipsoid_def (P, Q->cart);
- has_xy_grids = pj_param(P->ctx, P->params, "txy_grids").i;
- has_z_grids = pj_param(P->ctx, P->params, "tz_grids").i;
+ int has_xy_grids = pj_param(P->ctx, P->params, "txy_grids").i;
+ int has_z_grids = pj_param(P->ctx, P->params, "tz_grids").i;
+ int has_grids = pj_param(P->ctx, P->params, "tgrids").i;
/* Build gridlists. Both horizontal and vertical grids are mandatory. */
- if (!has_xy_grids || !has_z_grids) {
- proj_log_error(P, "deformation: Both +xy_grids and +z_grids should be specified.");
+ if ( !has_grids && (!has_xy_grids || !has_z_grids)) {
+ proj_log_error(P, "deformation: Either +grids or (+xy_grids and +z_grids) should be specified.");
return destructor(P, PJD_ERR_NO_ARGS );
}
- proj_hgrid_init(P, "xy_grids");
- if (proj_errno(P)) {
- proj_log_error(P, "deformation: could not find requested xy_grid(s).");
- return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID);
+ if( has_grids )
+ {
+ Q->grids = pj_generic_grid_init(P, "grids");
+ /* Was gridlist compiled properly? */
+ if ( proj_errno(P) ) {
+ proj_log_error(P, "deformation: could not find required grid(s).");
+ return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID);
+ }
}
-
- proj_vgrid_init(P, "z_grids");
- if (proj_errno(P)) {
- proj_log_error(P, "deformation: could not find requested z_grid(s).");
- return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID);
+ else
+ {
+ Q->hgrids = pj_hgrid_init(P, "xy_grids");
+ if (proj_errno(P)) {
+ proj_log_error(P, "deformation: could not find requested xy_grid(s).");
+ return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID);
+ }
+
+ Q->vgrids = pj_vgrid_init(P, "z_grids");
+ if (proj_errno(P)) {
+ proj_log_error(P, "deformation: could not find requested z_grid(s).");
+ return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID);
+ }
}
Q->dt = HUGE_VAL;
@@ -325,7 +431,6 @@ PJ *TRANSFORMATION(deformation,1) {
P->left = PJ_IO_UNITS_CARTESIAN;
P->right = PJ_IO_UNITS_CARTESIAN;
- P->destructor = destructor;
return P;
}
diff --git a/src/transformations/hgridshift.cpp b/src/transformations/hgridshift.cpp
index 90633939..122a7ab2 100644
--- a/src/transformations/hgridshift.cpp
+++ b/src/transformations/hgridshift.cpp
@@ -6,24 +6,38 @@
#include <time.h>
#include "proj_internal.h"
+#include "grids.hpp"
PROJ_HEAD(hgridshift, "Horizontal grid shift");
+using namespace NS_PROJ;
+
namespace { // anonymous namespace
-struct pj_opaque_hgridshift {
- double t_final;
- double t_epoch;
+struct hgridshiftData {
+ double t_final = 0;
+ double t_epoch = 0;
+ ListOfHGrids grids{};
+ bool defer_grid_opening = false;
};
} // anonymous namespace
static PJ_XYZ forward_3d(PJ_LPZ lpz, PJ *P) {
+ auto Q = static_cast<hgridshiftData*>(P->opaque);
PJ_COORD point = {{0,0,0,0}};
point.lpz = lpz;
- if (P->gridlist != nullptr) {
+ if ( Q->defer_grid_opening ) {
+ Q->defer_grid_opening = false;
+ Q->grids = pj_hgrid_init(P, "grids");
+ if ( proj_errno(P) ) {
+ return proj_coord_error().xyz;
+ }
+ }
+
+ if (!Q->grids.empty()) {
/* Only try the gridshift if at least one grid is loaded,
* otherwise just pass the coordinate through unchanged. */
- point.lp = proj_hgrid_apply(P, point.lp, PJ_FWD);
+ point.lp = pj_hgrid_apply(P->ctx, Q->grids, point.lp, PJ_FWD);
}
return point.xyz;
@@ -31,20 +45,29 @@ static PJ_XYZ forward_3d(PJ_LPZ lpz, PJ *P) {
static PJ_LPZ reverse_3d(PJ_XYZ xyz, PJ *P) {
+ auto Q = static_cast<hgridshiftData*>(P->opaque);
PJ_COORD point = {{0,0,0,0}};
point.xyz = xyz;
- if (P->gridlist != nullptr) {
+ if ( Q->defer_grid_opening ) {
+ Q->defer_grid_opening = false;
+ Q->grids = pj_hgrid_init(P, "grids");
+ if ( proj_errno(P) ) {
+ return proj_coord_error().lpz;
+ }
+ }
+
+ if (!Q->grids.empty()) {
/* Only try the gridshift if at least one grid is loaded,
* otherwise just pass the coordinate through unchanged. */
- point.lp = proj_hgrid_apply(P, point.lp, PJ_INV);
+ point.lp = pj_hgrid_apply(P->ctx, Q->grids, point.lp, PJ_INV);
}
return point.lpz;
}
static PJ_COORD forward_4d(PJ_COORD obs, PJ *P) {
- struct pj_opaque_hgridshift *Q = (struct pj_opaque_hgridshift *) P->opaque;
+ struct hgridshiftData *Q = (struct hgridshiftData *) P->opaque;
PJ_COORD point = obs;
/* If transformation is not time restricted, we always call it */
@@ -62,7 +85,7 @@ static PJ_COORD forward_4d(PJ_COORD obs, PJ *P) {
}
static PJ_COORD reverse_4d(PJ_COORD obs, PJ *P) {
- struct pj_opaque_hgridshift *Q = (struct pj_opaque_hgridshift *) P->opaque;
+ struct hgridshiftData *Q = (struct hgridshiftData *) P->opaque;
PJ_COORD point = obs;
/* If transformation is not time restricted, we always call it */
@@ -78,12 +101,29 @@ static PJ_COORD reverse_4d(PJ_COORD obs, PJ *P) {
return point;
}
+static PJ *destructor (PJ *P, int errlev) {
+ if (nullptr==P)
+ return nullptr;
+
+ delete static_cast<struct hgridshiftData*>(P->opaque);
+ P->opaque = nullptr;
+
+ return pj_default_destructor(P, errlev);
+}
+
+static void reassign_context( PJ* P, PJ_CONTEXT* ctx )
+{
+ auto Q = (struct hgridshiftData *) P->opaque;
+ for( auto& grid: Q->grids ) {
+ grid->reassign_context(ctx);
+ }
+}
PJ *TRANSFORMATION(hgridshift,0) {
- struct pj_opaque_hgridshift *Q = static_cast<struct pj_opaque_hgridshift*>(pj_calloc (1, sizeof (struct pj_opaque_hgridshift)));
- if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ auto Q = new hgridshiftData;
P->opaque = (void *) Q;
+ P->destructor = destructor;
+ P->reassign_context = reassign_context;
P->fwd4d = forward_4d;
P->inv4d = reverse_4d;
@@ -97,12 +137,12 @@ PJ *TRANSFORMATION(hgridshift,0) {
if (0==pj_param(P->ctx, P->params, "tgrids").i) {
proj_log_error(P, "hgridshift: +grids parameter missing.");
- return pj_default_destructor (P, PJD_ERR_NO_ARGS);
+ return destructor (P, PJD_ERR_NO_ARGS);
}
- /* TODO: Refactor into shared function that can be used */
- /* by both vgridshift and hgridshift */
- if (pj_param(P->ctx, P->params, "tt_final").i) {
+ /* TODO: Refactor into shared function that can be used */
+ /* by both vgridshift and hgridshift */
+ if (pj_param(P->ctx, P->params, "tt_final").i) {
Q->t_final = pj_param (P->ctx, P->params, "dt_final").f;
if (Q->t_final == 0) {
/* a number wasn't passed to +t_final, let's see if it was "now" */
@@ -117,16 +157,21 @@ PJ *TRANSFORMATION(hgridshift,0) {
}
}
- if (pj_param(P->ctx, P->params, "tt_epoch").i)
+ if (pj_param(P->ctx, P->params, "tt_epoch").i)
Q->t_epoch = pj_param (P->ctx, P->params, "dt_epoch").f;
- proj_hgrid_init(P, "grids");
- /* Was gridlist compiled properly? */
- if ( proj_errno(P) ) {
- proj_log_error(P, "hgridshift: could not find required grid(s).");
- return pj_default_destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID);
+ if( P->ctx->defer_grid_opening ) {
+ Q->defer_grid_opening = true;
}
+ else {
+ Q->grids = pj_hgrid_init(P, "grids");
+ /* Was gridlist compiled properly? */
+ if ( proj_errno(P) ) {
+ proj_log_error(P, "hgridshift: could not find required grid(s).");
+ return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID);
+ }
+ }
return P;
}
diff --git a/src/transformations/vgridshift.cpp b/src/transformations/vgridshift.cpp
index de0cdd8c..121b795a 100644
--- a/src/transformations/vgridshift.cpp
+++ b/src/transformations/vgridshift.cpp
@@ -6,26 +6,67 @@
#include <time.h>
#include "proj_internal.h"
+#include "grids.hpp"
PROJ_HEAD(vgridshift, "Vertical grid shift");
+using namespace NS_PROJ;
+
namespace { // anonymous namespace
-struct pj_opaque_vgridshift {
- double t_final;
- double t_epoch;
- double forward_multiplier;
+struct vgridshiftData {
+ double t_final = 0;
+ double t_epoch = 0;
+ double forward_multiplier = 0;
+ ListOfVGrids grids{};
+ bool defer_grid_opening = false;
};
} // anonymous namespace
+static void deal_with_vertcon_gtx_hack(PJ *P)
+{
+ struct vgridshiftData *Q = (struct vgridshiftData *) P->opaque;
+ // The .gtx VERTCON files stored millimetres, but the .tif files
+ // are in metres.
+ if( Q->forward_multiplier != 0.001 ) {
+ return;
+ }
+ const char* gridname = pj_param(P->ctx, P->params, "sgrids").s;
+ if( !gridname ) {
+ return;
+ }
+ if( strcmp(gridname, "vertconw.gtx") != 0 &&
+ strcmp(gridname, "vertconc.gtx") != 0 &&
+ strcmp(gridname, "vertcone.gtx") != 0 ) {
+ return;
+ }
+ if( Q->grids.empty() ) {
+ return;
+ }
+ const auto& grids = Q->grids[0]->grids();
+ if( !grids.empty() &&
+ grids[0]->name().find(".tif") != std::string::npos ) {
+ Q->forward_multiplier = 1.0;
+ }
+}
+
static PJ_XYZ forward_3d(PJ_LPZ lpz, PJ *P) {
- struct pj_opaque_vgridshift *Q = (struct pj_opaque_vgridshift *) P->opaque;
+ struct vgridshiftData *Q = (struct vgridshiftData *) P->opaque;
PJ_COORD point = {{0,0,0,0}};
point.lpz = lpz;
- if (P->vgridlist_geoid != nullptr) {
+ if ( Q->defer_grid_opening ) {
+ Q->defer_grid_opening = false;
+ Q->grids = pj_vgrid_init(P, "grids");
+ deal_with_vertcon_gtx_hack(P);
+ if ( proj_errno(P) ) {
+ return proj_coord_error().xyz;
+ }
+ }
+
+ if (!Q->grids.empty()) {
/* Only try the gridshift if at least one grid is loaded,
* otherwise just pass the coordinate through unchanged. */
- point.xyz.z += proj_vgrid_value(P, point.lp, Q->forward_multiplier);
+ point.xyz.z += pj_vgrid_value(P, Q->grids, point.lp, Q->forward_multiplier);
}
return point.xyz;
@@ -33,14 +74,23 @@ static PJ_XYZ forward_3d(PJ_LPZ lpz, PJ *P) {
static PJ_LPZ reverse_3d(PJ_XYZ xyz, PJ *P) {
- struct pj_opaque_vgridshift *Q = (struct pj_opaque_vgridshift *) P->opaque;
+ struct vgridshiftData *Q = (struct vgridshiftData *) P->opaque;
PJ_COORD point = {{0,0,0,0}};
point.xyz = xyz;
- if (P->vgridlist_geoid != nullptr) {
+ if ( Q->defer_grid_opening ) {
+ Q->defer_grid_opening = false;
+ Q->grids = pj_vgrid_init(P, "grids");
+ deal_with_vertcon_gtx_hack(P);
+ if ( proj_errno(P) ) {
+ return proj_coord_error().lpz;
+ }
+ }
+
+ if (!Q->grids.empty()) {
/* Only try the gridshift if at least one grid is loaded,
* otherwise just pass the coordinate through unchanged. */
- point.xyz.z -= proj_vgrid_value(P, point.lp, Q->forward_multiplier);
+ point.xyz.z -= pj_vgrid_value(P, Q->grids, point.lp, Q->forward_multiplier);
}
return point.lpz;
@@ -48,7 +98,7 @@ static PJ_LPZ reverse_3d(PJ_XYZ xyz, PJ *P) {
static PJ_COORD forward_4d(PJ_COORD obs, PJ *P) {
- struct pj_opaque_vgridshift *Q = (struct pj_opaque_vgridshift *) P->opaque;
+ struct vgridshiftData *Q = (struct vgridshiftData *) P->opaque;
PJ_COORD point = obs;
/* If transformation is not time restricted, we always call it */
@@ -66,7 +116,7 @@ static PJ_COORD forward_4d(PJ_COORD obs, PJ *P) {
}
static PJ_COORD reverse_4d(PJ_COORD obs, PJ *P) {
- struct pj_opaque_vgridshift *Q = (struct pj_opaque_vgridshift *) P->opaque;
+ struct vgridshiftData *Q = (struct vgridshiftData *) P->opaque;
PJ_COORD point = obs;
/* If transformation is not time restricted, we always call it */
@@ -82,16 +132,34 @@ static PJ_COORD reverse_4d(PJ_COORD obs, PJ *P) {
return point;
}
+static PJ *destructor (PJ *P, int errlev) {
+ if (nullptr==P)
+ return nullptr;
+
+ delete static_cast<struct vgridshiftData*>(P->opaque);
+ P->opaque = nullptr;
+
+ return pj_default_destructor(P, errlev);
+}
+
+static void reassign_context( PJ* P, PJ_CONTEXT* ctx )
+{
+ auto Q = (struct vgridshiftData *) P->opaque;
+ for( auto& grid: Q->grids ) {
+ grid->reassign_context(ctx);
+ }
+}
+
PJ *TRANSFORMATION(vgridshift,0) {
- struct pj_opaque_vgridshift *Q = static_cast<struct pj_opaque_vgridshift*>(pj_calloc (1, sizeof (struct pj_opaque_vgridshift)));
- if (nullptr==Q)
- return pj_default_destructor (P, ENOMEM);
+ auto Q = new vgridshiftData;
P->opaque = (void *) Q;
+ P->destructor = destructor;
+ P->reassign_context = reassign_context;
if (!pj_param(P->ctx, P->params, "tgrids").i) {
proj_log_error(P, "vgridshift: +grids parameter missing.");
- return pj_default_destructor(P, PJD_ERR_NO_ARGS);
+ return destructor(P, PJD_ERR_NO_ARGS);
}
/* TODO: Refactor into shared function that can be used */
@@ -120,13 +188,18 @@ PJ *TRANSFORMATION(vgridshift,0) {
Q->forward_multiplier = pj_param(P->ctx, P->params, "dmultiplier").f;
}
- /* Build gridlist. P->vgridlist_geoid can be empty if +grids only ask for optional grids. */
- proj_vgrid_init(P, "grids");
-
- /* Was gridlist compiled properly? */
- if ( proj_errno(P) ) {
- proj_log_error(P, "vgridshift: could not find required grid(s).");
- return pj_default_destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID);
+ if( P->ctx->defer_grid_opening ) {
+ Q->defer_grid_opening = true;
+ }
+ else {
+ /* Build gridlist. P->vgridlist_geoid can be empty if +grids only ask for optional grids. */
+ Q->grids = pj_vgrid_init(P, "grids");
+
+ /* Was gridlist compiled properly? */
+ if ( proj_errno(P) ) {
+ proj_log_error(P, "vgridshift: could not find required grid(s).");
+ return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID);
+ }
}
P->fwd4d = forward_4d;
diff --git a/src/transformations/xyzgridshift.cpp b/src/transformations/xyzgridshift.cpp
new file mode 100644
index 00000000..e1c76518
--- /dev/null
+++ b/src/transformations/xyzgridshift.cpp
@@ -0,0 +1,303 @@
+/******************************************************************************
+ * Project: PROJ
+ * Purpose: Geocentric translation using a grid
+ * Author: Even Rouault, <even.rouault at spatialys.com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2019, Even Rouault, <even.rouault at spatialys.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *****************************************************************************/
+
+#define PJ_LIB__
+
+#include <errno.h>
+#include <string.h>
+#include <stddef.h>
+#include <time.h>
+
+#include "proj_internal.h"
+#include "grids.hpp"
+
+#include <algorithm>
+
+PROJ_HEAD(xyzgridshift, "Geocentric grid shift");
+
+using namespace NS_PROJ;
+
+namespace { // anonymous namespace
+struct xyzgridshiftData {
+ PJ *cart = nullptr;
+ bool grid_ref_is_input = true;
+ ListOfGenericGrids grids{};
+ bool defer_grid_opening = false;
+ double multiplier = 1.0;
+};
+} // anonymous namespace
+
+// ---------------------------------------------------------------------------
+
+static bool get_grid_values(PJ* P,
+ xyzgridshiftData* Q,
+ const PJ_LP& lp,
+ double& dx,
+ double& dy,
+ double& dz)
+{
+ if ( Q->defer_grid_opening ) {
+ Q->defer_grid_opening = false;
+ Q->grids = pj_generic_grid_init(P, "grids");
+ if ( proj_errno(P) ) {
+ return false;
+ }
+ }
+
+ GenericShiftGridSet* gridset = nullptr;
+ auto grid = pj_find_generic_grid(Q->grids, lp, gridset);
+ if( !grid ) {
+ return false;
+ }
+ if( grid->isNullGrid() ) {
+ dx = 0;
+ dy = 0;
+ dz = 0;
+ return true;
+ }
+ const auto samplesPerPixel = grid->samplesPerPixel();
+ if( samplesPerPixel < 3 ) {
+ proj_log_error(P, "xyzgridshift: grid has not enough samples");
+ return false;
+ }
+ int sampleX = 0;
+ int sampleY = 1;
+ int sampleZ = 2;
+ for( int i = 0; i < samplesPerPixel; i++ )
+ {
+ const auto desc = grid->description(i);
+ if( desc == "x_translation") {
+ sampleX = i;
+ } else if( desc == "y_translation") {
+ sampleY = i;
+ } else if( desc == "z_translation") {
+ sampleZ = i;
+ }
+ }
+ const auto unit = grid->unit(sampleX);
+ if( !unit.empty() && unit != "metre" ) {
+ proj_log_error(P, "xyzgridshift: Only unit=metre currently handled");
+ return false;
+ }
+
+ bool must_retry = false;
+ if( !pj_bilinear_interpolation_three_samples(grid, lp,
+ sampleX, sampleY, sampleZ,
+ dx, dy, dz,
+ must_retry) )
+ {
+ if( must_retry )
+ return get_grid_values( P, Q, lp, dx, dy, dz);
+ return false;
+ }
+
+ dx *= Q->multiplier;
+ dy *= Q->multiplier;
+ dz *= Q->multiplier;
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+
+#define SQUARE(x) ((x)*(x))
+
+// ---------------------------------------------------------------------------
+
+static PJ_COORD iterative_adjustment(PJ* P,
+ xyzgridshiftData* Q,
+ const PJ_COORD& pointInit,
+ double factor)
+{
+ PJ_COORD point = pointInit;
+ for(int i = 0; i < 10; i++) {
+ PJ_COORD geodetic;
+ geodetic.lpz = pj_inv3d(point.xyz, Q->cart);
+
+ double dx, dy, dz;
+ if( !get_grid_values(P, Q, geodetic.lp, dx, dy, dz) ) {
+ return proj_coord_error();
+ }
+
+ dx *= factor;
+ dy *= factor;
+ dz *= factor;
+
+ const double err = SQUARE((point.xyz.x - pointInit.xyz.x) - dx) +
+ SQUARE((point.xyz.y - pointInit.xyz.y) - dy) +
+ SQUARE((point.xyz.z - pointInit.xyz.z) - dz);
+
+ point.xyz.x = pointInit.xyz.x + dx;
+ point.xyz.y = pointInit.xyz.y + dy;
+ point.xyz.z = pointInit.xyz.z + dz;
+ if( err < 1e-10 ) {
+ break;
+ }
+ }
+ return point;
+}
+
+// ---------------------------------------------------------------------------
+
+static PJ_COORD direct_adjustment(PJ* P,
+ xyzgridshiftData* Q,
+ PJ_COORD point,
+ double factor)
+{
+ PJ_COORD geodetic;
+ geodetic.lpz = pj_inv3d(point.xyz, Q->cart);
+
+ double dx, dy, dz;
+ if( !get_grid_values(P, Q, geodetic.lp, dx, dy, dz) ) {
+ return proj_coord_error();
+ }
+ point.xyz.x += factor * dx;
+ point.xyz.y += factor * dy;
+ point.xyz.z += factor * dz;
+ return point;
+}
+
+// ---------------------------------------------------------------------------
+
+static PJ_XYZ forward_3d(PJ_LPZ lpz, PJ *P) {
+ auto Q = static_cast<xyzgridshiftData*>(P->opaque);
+ PJ_COORD point = {{0,0,0,0}};
+ point.lpz = lpz;
+
+ if( Q->grid_ref_is_input ) {
+ point = direct_adjustment(P, Q, point, 1.0);
+ }
+ else {
+ point = iterative_adjustment(P, Q, point, 1.0);
+ }
+
+ return point.xyz;
+}
+
+
+static PJ_LPZ reverse_3d(PJ_XYZ xyz, PJ *P) {
+ auto Q = static_cast<xyzgridshiftData*>(P->opaque);
+ PJ_COORD point = {{0,0,0,0}};
+ point.xyz = xyz;
+
+ if( Q->grid_ref_is_input ) {
+ point = iterative_adjustment(P, Q, point, -1.0);
+ }
+ else {
+ point = direct_adjustment(P, Q, point, -1.0);
+ }
+
+ return point.lpz;
+}
+
+static PJ *destructor (PJ *P, int errlev) {
+ if (nullptr==P)
+ return nullptr;
+
+ auto Q = static_cast<struct xyzgridshiftData*>(P->opaque);
+ if( Q )
+ {
+ if (Q->cart)
+ Q->cart->destructor (Q->cart, errlev);
+ delete Q;
+ }
+ P->opaque = nullptr;
+
+ return pj_default_destructor(P, errlev);
+}
+
+static void reassign_context( PJ* P, PJ_CONTEXT* ctx )
+{
+ auto Q = (struct xyzgridshiftData *) P->opaque;
+ for( auto& grid: Q->grids ) {
+ grid->reassign_context(ctx);
+ }
+}
+
+
+PJ *TRANSFORMATION(xyzgridshift,0) {
+ auto Q = new xyzgridshiftData;
+ P->opaque = (void *) Q;
+ P->destructor = destructor;
+ P->reassign_context = reassign_context;
+
+ P->fwd4d = nullptr;
+ P->inv4d = nullptr;
+ P->fwd3d = forward_3d;
+ P->inv3d = reverse_3d;
+ P->fwd = nullptr;
+ P->inv = nullptr;
+
+ P->left = PJ_IO_UNITS_CARTESIAN;
+ P->right = PJ_IO_UNITS_CARTESIAN;
+
+ // Pass a dummy ellipsoid definition that will be overridden just afterwards
+ Q->cart = proj_create(P->ctx, "+proj=cart +a=1");
+ if (Q->cart == nullptr)
+ return destructor(P, ENOMEM);
+
+ /* inherit ellipsoid definition from P to Q->cart */
+ pj_inherit_ellipsoid_def (P, Q->cart);
+
+ const char* grid_ref = pj_param (P->ctx, P->params, "sgrid_ref").s;
+ if( grid_ref ) {
+ if (strcmp(grid_ref, "input_crs") == 0 ) {
+ // default
+ } else if (strcmp(grid_ref, "output_crs") == 0 ) {
+ // Convention use for example for NTF->RGF93 grid that contains
+ // delta x,y,z from NTF to RGF93, but the grid itself is referenced
+ // in RGF93
+ Q->grid_ref_is_input = false;
+ } else {
+ proj_log_error(P, "xyzgridshift: unusupported value for grid_ref");
+ return destructor (P, PJD_ERR_NO_ARGS);
+ }
+ }
+
+ if (0==pj_param(P->ctx, P->params, "tgrids").i) {
+ proj_log_error(P, "xyzgridshift: +grids parameter missing.");
+ return destructor (P, PJD_ERR_NO_ARGS);
+ }
+
+ /* multiplier for delta x,y,z */
+ if (pj_param(P->ctx, P->params, "tmultiplier").i) {
+ Q->multiplier = pj_param(P->ctx, P->params, "dmultiplier").f;
+ }
+
+ if( P->ctx->defer_grid_opening ) {
+ Q->defer_grid_opening = true;
+ }
+ else {
+ Q->grids = pj_generic_grid_init(P, "grids");
+ /* Was gridlist compiled properly? */
+ if ( proj_errno(P) ) {
+ proj_log_error(P, "xyzgridshift: could not find required grid(s).");
+ return destructor(P, PJD_ERR_FAILED_TO_LOAD_GRID);
+ }
+ }
+
+ return P;
+}