aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorEven Rouault <even.rouault@spatialys.com>2020-01-29 12:51:49 +0100
committerGitHub <noreply@github.com>2020-01-29 12:51:49 +0100
commitbf6b1a889b12d7ecdcb190cd14155e09e132095f (patch)
treef742983990ca914ff3044352efd4e01eef5bd01d /src
parent74a10a8de03deb823690f143e191087bf7c4821f (diff)
parentb113d0825cf82f66f738746db0f21745017552e9 (diff)
downloadPROJ-bf6b1a889b12d7ecdcb190cd14155e09e132095f.tar.gz
PROJ-bf6b1a889b12d7ecdcb190cd14155e09e132095f.zip
Merge pull request #1891 from rouault/rfc5
Implement RFC5: Adopt GeoTIFF-based grids for grids delivered with PROJ
Diffstat (limited to 'src')
-rw-r--r--src/filemanager.cpp202
-rw-r--r--src/filemanager.hpp3
-rw-r--r--src/init.cpp48
-rw-r--r--src/iso19111/coordinateoperation.cpp74
-rw-r--r--src/iso19111/factory.cpp27
-rw-r--r--src/proj_constants.h2
6 files changed, 264 insertions, 92 deletions
diff --git a/src/filemanager.cpp b/src/filemanager.cpp
index 9263e9fe..ac753012 100644
--- a/src/filemanager.cpp
+++ b/src/filemanager.cpp
@@ -40,6 +40,8 @@
#include "filemanager.hpp"
#include "proj.h"
#include "proj/internal/internal.hpp"
+#include "proj/internal/io_internal.hpp"
+#include "proj/io.hpp"
#include "proj_internal.h"
#include <sys/stat.h>
@@ -68,6 +70,62 @@ File::File(const std::string &name) : name_(name) {}
File::~File() = default;
+// ---------------------------------------------------------------------------
+
+std::string File::read_line(size_t maxLen, bool &maxLenReached,
+ bool &eofReached) {
+ constexpr size_t MAX_MAXLEN = 1024 * 1024;
+ maxLen = std::min(maxLen, MAX_MAXLEN);
+ while (true) {
+ // Consume existing lines in buffer
+ size_t pos = readLineBuffer_.find_first_of("\r\n");
+ if (pos != std::string::npos) {
+ if (pos > maxLen) {
+ std::string ret(readLineBuffer_.substr(0, maxLen));
+ readLineBuffer_ = readLineBuffer_.substr(maxLen);
+ maxLenReached = true;
+ eofReached = false;
+ return ret;
+ }
+ std::string ret(readLineBuffer_.substr(0, pos));
+ if (readLineBuffer_[pos] == '\r' &&
+ readLineBuffer_[pos + 1] == '\n') {
+ pos += 1;
+ }
+ readLineBuffer_ = readLineBuffer_.substr(pos + 1);
+ maxLenReached = false;
+ eofReached = false;
+ return ret;
+ }
+
+ const size_t prevSize = readLineBuffer_.size();
+ if (maxLen <= prevSize) {
+ std::string ret(readLineBuffer_.substr(0, maxLen));
+ readLineBuffer_ = readLineBuffer_.substr(maxLen);
+ maxLenReached = true;
+ eofReached = false;
+ return ret;
+ }
+
+ if (eofReadLine_) {
+ std::string ret = readLineBuffer_;
+ readLineBuffer_.clear();
+ maxLenReached = false;
+ eofReached = ret.empty();
+ return ret;
+ }
+
+ readLineBuffer_.resize(maxLen);
+ const size_t nRead =
+ read(&readLineBuffer_[prevSize], maxLen - prevSize);
+ if (nRead < maxLen - prevSize)
+ eofReadLine_ = true;
+ readLineBuffer_.resize(prevSize + nRead);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
#ifdef _WIN32
/* The bulk of utf8towc()/utf8fromwc() is derived from the utf.c module from
@@ -1460,6 +1518,20 @@ static void *pj_open_file_with_manager(projCtx ctx, const char *name,
.release();
}
+// ---------------------------------------------------------------------------
+
+static NS_PROJ::io::DatabaseContextPtr getDBcontext(PJ_CONTEXT *ctx) {
+ try {
+ if (ctx->cpp_context == nullptr) {
+ ctx->cpp_context = new projCppContext(ctx);
+ }
+ return ctx->cpp_context->getDatabaseContext().as_nullable();
+ } catch (const std::exception &e) {
+ pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what());
+ return nullptr;
+ }
+}
+
/************************************************************************/
/* FileManager::open_resource_file() */
/************************************************************************/
@@ -1475,27 +1547,54 @@ NS_PROJ::FileManager::open_resource_file(projCtx ctx, const char *name) {
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
+ // Retry with the new proj grid name 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)));
+
+ auto dbContext = getDBcontext(ctx);
+ if (dbContext) {
+ try {
+ auto filename = dbContext->getProjGridName(name);
+ if (!filename.empty()) {
+ 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);
+ }
+ }
+ } catch (const std::exception &e) {
+ pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what());
+ return nullptr;
+ }
}
- if (file) {
- pj_ctx_set_errno(ctx, 0);
+ }
+ // Retry with the old proj grid name if the file name ends with .tif
+ else if (file == nullptr && !is_tilde_slash(name) &&
+ !is_rel_or_absolute_filename(name) &&
+ !starts_with(name, "http://") && !starts_with(name, "https://") &&
+ strstr(name, ".tif") != nullptr) {
+
+ auto dbContext = getDBcontext(ctx);
+ if (dbContext) {
+ try {
+ auto filename = dbContext->getOldProjGridName(name);
+ if (!filename.empty()) {
+ 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);
+ }
+ }
+ } catch (const std::exception &e) {
+ pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what());
+ return nullptr;
+ }
}
}
@@ -1508,35 +1607,12 @@ NS_PROJ::FileManager::open_resource_file(projCtx ctx, const char *name) {
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);
- }
- }
+ 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);
}
}
}
@@ -1571,7 +1647,8 @@ PAFile pj_open_lib(projCtx ctx, const char *name, const char *mode) {
* as a short filename.
*
* @param ctx context.
- * @param short_filename short filename (e.g. egm96_15.gtx). Must not be NULL.
+ * @param short_filename short filename (e.g. us_nga_egm96_15.tif).
+ * 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.
@@ -1580,14 +1657,33 @@ PAFile pj_open_lib(projCtx ctx, const char *name, const char *mode) {
*/
int pj_find_file(projCtx ctx, const char *short_filename,
char *out_full_filename, size_t out_full_filename_size) {
- 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;
+ auto file = std::unique_ptr<NS_PROJ::File>(
+ 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)));
+
+ // Retry with the old proj grid name if the file name ends with .tif
+ if (file == nullptr && strstr(short_filename, ".tif") != nullptr) {
+
+ auto dbContext = getDBcontext(ctx);
+ if (dbContext) {
+ try {
+ auto filename = dbContext->getOldProjGridName(short_filename);
+ if (!filename.empty()) {
+ file.reset(reinterpret_cast<NS_PROJ::File *>(
+ pj_open_lib_internal(ctx, filename.c_str(), "rb",
+ pj_open_file_with_manager,
+ out_full_filename,
+ out_full_filename_size)));
+ }
+ } catch (const std::exception &e) {
+ pj_log(ctx, PJ_LOG_DEBUG, "%s", e.what());
+ return false;
+ }
+ }
}
- return 0;
+
+ return file != nullptr;
}
/************************************************************************/
diff --git a/src/filemanager.hpp b/src/filemanager.hpp
index 787af342..bbd12b7e 100644
--- a/src/filemanager.hpp
+++ b/src/filemanager.hpp
@@ -76,6 +76,8 @@ class FileManager {
class File {
protected:
std::string name_;
+ std::string readLineBuffer_{};
+ bool eofReadLine_ = false;
explicit File(const std::string &name);
public:
@@ -86,6 +88,7 @@ class File {
virtual unsigned long long tell() = 0;
virtual void reassign_context(PJ_CONTEXT *ctx) = 0;
virtual bool hasChanged() const = 0;
+ std::string read_line(size_t maxLen, bool &maxLenReached, bool &eofReached);
const std::string &name() const { return name_; }
};
diff --git a/src/init.cpp b/src/init.cpp
index c1683285..9c7b7b19 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -39,6 +39,7 @@
#include "geodesic.h"
#include "proj.h"
#include "proj_internal.h"
+#include "filemanager.hpp"
#include <math.h>
@@ -81,18 +82,10 @@ static char *get_init_string (PJ_CONTEXT *ctx, const char *name) {
char *fname, *section;
const char *key;
char *buffer = nullptr;
- char *line = nullptr;
- PAFile fid;
size_t n;
-
- line = static_cast<char*>(pj_malloc (MAX_LINE_LENGTH + 1));
- if (nullptr==line)
- return nullptr;
-
fname = static_cast<char*>(pj_malloc (MAX_PATH_FILENAME+ID_TAG_MAX+3));
if (nullptr==fname) {
- pj_dealloc (line);
return nullptr;
}
@@ -104,7 +97,6 @@ static char *get_init_string (PJ_CONTEXT *ctx, const char *name) {
key += 5;
if (MAX_PATH_FILENAME + ID_TAG_MAX + 2 < strlen (key)) {
pj_dealloc (fname);
- pj_dealloc (line);
return nullptr;
}
memmove (fname, key, strlen (key) + 1);
@@ -114,7 +106,6 @@ static char *get_init_string (PJ_CONTEXT *ctx, const char *name) {
if (nullptr==section) {
proj_context_errno_set (ctx, PJD_ERR_NO_COLON_IN_INIT_STRING);
pj_dealloc (fname);
- pj_dealloc (line);
return nullptr;
}
*section = 0;
@@ -124,36 +115,36 @@ static char *get_init_string (PJ_CONTEXT *ctx, const char *name) {
"get_init_string: searching for section [%s] in init file [%s]",
section, fname);
- fid = pj_open_lib (ctx, fname, "rt");
- if (nullptr==fid) {
+ auto file = NS_PROJ::FileManager::open_resource_file(ctx, fname);
+ if (nullptr==file) {
pj_dealloc (fname);
- pj_dealloc (line);
proj_context_errno_set (ctx, PJD_ERR_NO_OPTION_IN_INIT_FILE);
return nullptr;
}
/* Search for section in init file */
+ std::string line;
for (;;) {
+ bool eofReached = false;
+ bool maxLenReached = false;
+ line = file->read_line(MAX_LINE_LENGTH, maxLenReached, eofReached);
/* End of file? */
- if (nullptr==pj_ctx_fgets (ctx, line, MAX_LINE_LENGTH, fid)) {
- pj_dealloc (buffer);
+ if (maxLenReached || eofReached) {
pj_dealloc (fname);
- pj_dealloc (line);
- pj_ctx_fclose (ctx, fid);
proj_context_errno_set (ctx, PJD_ERR_NO_OPTION_IN_INIT_FILE);
return nullptr;
}
/* At start of right section? */
- pj_chomp (line);
+ pj_chomp (&line[0]);
if ('<'!=line[0])
continue;
- if (strlen (line) < n + 2)
+ if (strlen (line.c_str()) < n + 2)
continue;
if (line[n + 1] != '>')
continue;
- if (0==strncmp (line + 1, section, n))
+ if (0==strncmp (line.data() + 1, section, n))
break;
}
@@ -161,13 +152,11 @@ static char *get_init_string (PJ_CONTEXT *ctx, const char *name) {
buffer = static_cast<char*>(pj_malloc (current_buffer_size));
if (nullptr==buffer) {
pj_dealloc (fname);
- pj_dealloc (line);
- pj_ctx_fclose (ctx, fid);
return nullptr;
}
/* Skip the "<section>" indicator, and copy the rest of the line over */
- strcpy (buffer, line + strlen (section) + 2);
+ strcpy (buffer, line.data() + strlen (section) + 2);
/* Copy the remaining lines of the section to buffer */
for (;;) {
@@ -181,15 +170,18 @@ static char *get_init_string (PJ_CONTEXT *ctx, const char *name) {
break;
}
+ bool eofReached = false;
+ bool maxLenReached = false;
+ line = file->read_line(MAX_LINE_LENGTH, maxLenReached, eofReached);
/* End of file? - done! */
- if (nullptr==pj_ctx_fgets (ctx, line, MAX_LINE_LENGTH, fid))
+ if (maxLenReached || eofReached)
break;
/* Otherwise, handle the line. It MAY be the start of the next section, */
/* but that will be handled at the start of next trip through the loop */
buffer_length = strlen (buffer);
- pj_chomp (line); /* Remove '#' style comments */
- next_length = strlen (line) + buffer_length + 2;
+ pj_chomp (&line[0]); /* Remove '#' style comments */
+ next_length = strlen (line.data()) + buffer_length + 2;
if (next_length > current_buffer_size) {
char *b = static_cast<char*>(pj_malloc (2 * current_buffer_size));
if (nullptr==b) {
@@ -203,12 +195,10 @@ static char *get_init_string (PJ_CONTEXT *ctx, const char *name) {
buffer = b;
}
buffer[buffer_length] = ' ';
- strcpy (buffer + buffer_length + 1, line);
+ strcpy (buffer + buffer_length + 1, line.data());
}
- pj_ctx_fclose (ctx, fid);
pj_dealloc (fname);
- pj_dealloc (line);
if (nullptr==buffer)
return nullptr;
pj_shrink (buffer);
diff --git a/src/iso19111/coordinateoperation.cpp b/src/iso19111/coordinateoperation.cpp
index 6eef6de8..8ca95223 100644
--- a/src/iso19111/coordinateoperation.cpp
+++ b/src/iso19111/coordinateoperation.cpp
@@ -8448,6 +8448,29 @@ static const std::string &_getCTABLE2Filename(const Transformation *op,
//! @cond Doxygen_Suppress
static const std::string &
+_getHorizontalShiftGTIFFFilename(const Transformation *op, bool allowInverse) {
+ const auto &l_method = op->method();
+ const auto &methodName = l_method->nameStr();
+ if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF) ||
+ (allowInverse &&
+ ci_equal(methodName,
+ INVERSE_OF + PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF))) {
+ const auto &fileParameter = op->parameterValue(
+ EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE,
+ EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE);
+ if (fileParameter &&
+ fileParameter->type() == ParameterValue::Type::FILENAME) {
+ return fileParameter->valueFile();
+ }
+ }
+ return nullString;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+static const std::string &
_getHeightToGeographic3DFilename(const Transformation *op, bool allowInverse) {
const auto &methodName = op->method()->nameStr();
@@ -8658,7 +8681,30 @@ TransformationNNPtr Transformation::substitutePROJAlternativeGridNames(
const auto &l_sourceCRS = sourceCRS();
const auto &l_targetCRS = targetCRS();
const auto &l_accuracies = coordinateOperationAccuracies();
- if (projGridFormat == "NTv1") {
+ if (projGridFormat == "GTiff") {
+ auto parameters =
+ std::vector<OperationParameterNNPtr>{createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)};
+ auto methodProperties = util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF);
+ auto values = std::vector<ParameterValueNNPtr>{
+ ParameterValue::createFilename(projFilename)};
+ if (inverseDirection) {
+ return create(createPropertiesForInverse(
+ self.as_nullable().get(), true, false),
+ l_targetCRS, l_sourceCRS, nullptr,
+ methodProperties, parameters, values,
+ l_accuracies)
+ ->inverseAsTransformation();
+
+ } else {
+ return create(createSimilarPropertiesTransformation(self),
+ l_sourceCRS, l_targetCRS, nullptr,
+ methodProperties, parameters, values,
+ l_accuracies);
+ }
+ } else if (projGridFormat == "NTv1") {
if (inverseDirection) {
return createNTv1(createPropertiesForInverse(
self.as_nullable().get(), true, false),
@@ -9336,10 +9382,14 @@ void Transformation::_exportToPROJString(
const auto &NTv1Filename = _getNTv1Filename(this, true);
const auto &NTv2Filename = _getNTv2Filename(this, true);
const auto &CTABLE2Filename = _getCTABLE2Filename(this, true);
+ const auto &HorizontalShiftGTIFFFilename =
+ _getHorizontalShiftGTIFFFilename(this, true);
const auto &hGridShiftFilename =
- !NTv1Filename.empty() ? NTv1Filename : !NTv2Filename.empty()
- ? NTv2Filename
- : CTABLE2Filename;
+ !HorizontalShiftGTIFFFilename.empty()
+ ? HorizontalShiftGTIFFFilename
+ : !NTv1Filename.empty() ? NTv1Filename : !NTv2Filename.empty()
+ ? NTv2Filename
+ : CTABLE2Filename;
if (!hGridShiftFilename.empty()) {
auto sourceCRSGeog =
dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
@@ -9417,11 +9467,15 @@ void Transformation::_exportToPROJString(
if (fileParameter &&
fileParameter->type() == ParameterValue::Type::FILENAME) {
formatter->addStep("vgridshift");
- // The vertcon grids go from NGVD 29 to NAVD 88, with units
- // in millimeter (see
- // https://github.com/OSGeo/proj.4/issues/1071)
formatter->addParam("grids", fileParameter->valueFile());
- formatter->addParam("multiplier", 0.001);
+ if (fileParameter->valueFile().find(".tif") != std::string::npos) {
+ formatter->addParam("multiplier", 1.0);
+ } else {
+ // The vertcon grids go from NGVD 29 to NAVD 88, with units
+ // in millimeter (see
+ // https://github.com/OSGeo/proj.4/issues/1071), for gtx files
+ formatter->addParam("multiplier", 0.001);
+ }
return;
}
}
@@ -11155,7 +11209,9 @@ struct FilterResults {
!gridDesc.available) {
gridsAvailable = false;
}
- if (gridDesc.packageName.empty() && !gridDesc.available) {
+ if (gridDesc.packageName.empty() &&
+ !(!gridDesc.url.empty() && gridDesc.openLicense) &&
+ !gridDesc.available) {
gridsKnown = false;
}
}
diff --git a/src/iso19111/factory.cpp b/src/iso19111/factory.cpp
index 7e680d31..54aa993f 100644
--- a/src/iso19111/factory.cpp
+++ b/src/iso19111/factory.cpp
@@ -987,7 +987,8 @@ bool DatabaseContext::lookForGridInfo(
openLicense = (row[3].empty() ? row[4] : row[3]) == "1";
directDownload = (row[5].empty() ? row[6] : row[5]) == "1";
- if (considerKnownGridsAsAvailable && !packageName.empty()) {
+ if (considerKnownGridsAsAvailable &&
+ (!packageName.empty() || (!url.empty() && openLicense))) {
gridAvailable = true;
}
@@ -1012,6 +1013,30 @@ bool DatabaseContext::isKnownName(const std::string &name,
sql += "\" WHERE name = ? LIMIT 1";
return !d->run(sql, {name}).empty();
}
+// ---------------------------------------------------------------------------
+
+std::string
+DatabaseContext::getProjGridName(const std::string &oldProjGridName) {
+ auto res = d->run("SELECT proj_grid_name FROM grid_alternatives WHERE "
+ "old_proj_grid_name = ?",
+ {oldProjGridName});
+ if (res.empty()) {
+ return std::string();
+ }
+ return res.front()[0];
+}
+
+// ---------------------------------------------------------------------------
+
+std::string DatabaseContext::getOldProjGridName(const std::string &gridName) {
+ auto res = d->run("SELECT old_proj_grid_name FROM grid_alternatives WHERE "
+ "proj_grid_name = ?",
+ {gridName});
+ if (res.empty()) {
+ return std::string();
+ }
+ return res.front()[0];
+}
// ---------------------------------------------------------------------------
diff --git a/src/proj_constants.h b/src/proj_constants.h
index d5b484c6..54dc7873 100644
--- a/src/proj_constants.h
+++ b/src/proj_constants.h
@@ -508,6 +508,8 @@
#define PROJ_WKT2_NAME_METHOD_CTABLE2 "CTABLE2"
+#define PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF "HORIZONTAL_SHIFT_GTIFF"
+
/* ------------------------------------------------------------------------ */
#define EPSG_CODE_METHOD_VERTCON 9658