From ee9e231ccb4dc35fa840a9644be5e88ab11c3a63 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 7 Mar 2024 15:15:47 +0200 Subject: [PATCH] Add GDALRegisterTransformer() to public gdal_alg.h API and convert existing GCP_POLYNOMIAL, GCP_TPS, TPS and GeoLoc transformers to use it. This also allows custom spatial point transformers to be registered and used by GDALCreateGenImgProjTransformer(), and in warped VRT. --- alg/gdal_alg.h | 68 ++- alg/gdal_alg_priv.h | 9 +- alg/gdal_crs.cpp | 9 +- alg/gdal_rpc.cpp | 11 +- alg/gdal_tps.cpp | 9 +- alg/gdalgeoloc.cpp | 9 +- alg/gdaltransformer.cpp | 947 ++++++++++++++++++++---------------- gcore/gdaldrivermanager.cpp | 4 +- 8 files changed, 616 insertions(+), 450 deletions(-) diff --git a/alg/gdal_alg.h b/alg/gdal_alg.h index 9b1daf10c683..845ae8e2e06c 100644 --- a/alg/gdal_alg.h +++ b/alg/gdal_alg.h @@ -96,21 +96,83 @@ typedef int (*GDALTransformerFunc)(void *pTransformerArg, int bDstToSrc, int nPointCount, double *x, double *y, double *z, int *panSuccess); -/*! @cond Doxygen_Suppress */ +/** Signature to be set in GDALTransformerInfo::abySignature member */ #define GDAL_GTI2_SIGNATURE "GTI2" +/** Structure that must be instantiated as the first member of a transformer + * instance. + */ typedef struct { + /** Signature. Should be filled with GDAL_GTI2_SIGNATURE. */ GByte abySignature[4]; + + /** Class name. Should uniquely identify the transformer. */ const char *pszClassName; + + /** Transformer callback. */ GDALTransformerFunc pfnTransform; + + /** Cleanup callback. */ void (*pfnCleanup)(void *pTransformerArg); + + /** XML serialization callback. */ CPLXMLNode *(*pfnSerialize)(void *pTransformerArg); + + /** Callback to instanciate a transformer instance similar to the passed one. */ void *(*pfnCreateSimilar)(void *pTransformerArg, double dfSrcRatioX, double dfSrcRatioY); } GDALTransformerInfo; -/*! @endcond */ +/* Custom transformers */ + +/** Function pointer that instantiates a transformer instance for use by the + * GDALCreateGenImgProjTransformer() family of functions. + * + * The transformer instance should be a structure whose first member is an + * instance of GDALTransformerInfo. + * + * @param pszMethod Transformation method. May be NULL, in which case the + * callback is free to decide, from the content of the dataset + * and transformer options, if it can be instantiated. + * @param bIsSrcTransformer true if the transformer is used as the source + * transformer of GDALGenImgProjTransform. + * false if the transformer is used as the target + * transformer of GDALGenImgProjTransform. + * @param hDS source or target dataset, depending of bIsSrcTransformer. + * The passed value is not NULL. + * @param papszTransformOptions Transformer options. May be NULL. + * @param[in,out] phSRS Pointer to a OGRSpatialReferenceH. The input *phSRS may be + * set. The callback may assign a new value to *phSRS (to be + * released with OSRRelease(). If the callback needs to free + * the passed *phSRS value, it must do so with OSRRelease(). + * @return a new transformer instance, or NULL in case of error or if + * pszMethod == NULL and the callback does not recognize it should be + * instantiated. + */ +typedef void *(*GDALTransformerCreateForGenImgTransformer)( + const char *pszMethod, bool bIsSrcTransformer, GDALDatasetH hDS, + char **papszTransformOptions, OGRSpatialReferenceH *phSRS); + +/** Function pointer that takes a XML tree as argument and returns a + * transformer instance. + * + * The transformer instance returned should be a structure whose first member + * is an instance of GDALTransformerInfo. + * + * @param psTree XML tree + * @return a new transformer instance, or NULL in case of error + */ +typedef void *(*GDALTransformDeserializeFunc)(const CPLXMLNode *psTree); + +void CPL_DLL * +GDALRegisterTransformer(const char *pszTransformName, + GDALTransformerFunc pfnTransformerFunc, + GDALTransformDeserializeFunc pfnDeserializeFunc, + GDALTransformerCreateForGenImgTransformer + pfnTransformerCreateForGenImgTransformerFunc); + +void CPL_DLL GDALUnregisterTransformer(void *pData); /*! @cond Doxygen_Suppress */ void CPL_DLL GDALDestroyTransformer(void *pTransformerArg); @@ -246,6 +308,8 @@ int CPL_DLL GDALApproxTransform(void *pTransformArg, int bDstToSrc, int nPointCount, double *x, double *y, double *z, int *panSuccess); +/* Warping related functions */ + int CPL_DLL CPL_STDCALL GDALSimpleImageWarp( GDALDatasetH hSrcDS, GDALDatasetH hDstDS, int nBandCount, int *panBandList, GDALTransformerFunc pfnTransform, void *pTransformArg, diff --git a/alg/gdal_alg_priv.h b/alg/gdal_alg_priv.h index b7b3a3a35ff2..3813deb38122 100644 --- a/alg/gdal_alg_priv.h +++ b/alg/gdal_alg_priv.h @@ -176,14 +176,7 @@ constexpr const char *GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME = bool GDALIsTransformer(void *hTransformerArg, const char *pszClassName); -typedef void *(*GDALTransformDeserializeFunc)(CPLXMLNode *psTree); - -void CPL_DLL *GDALRegisterTransformDeserializer( - const char *pszTransformName, GDALTransformerFunc pfnTransformerFunc, - GDALTransformDeserializeFunc pfnDeserializeFunc); -void CPL_DLL GDALUnregisterTransformDeserializer(void *pData); - -void GDALCleanupTransformDeserializerMutex(); +void GDALCleanupTransformers(); /* Transformer cloning */ diff --git a/alg/gdal_crs.cpp b/alg/gdal_crs.cpp index 74ea4ceff9a9..9aa423108709 100644 --- a/alg/gdal_crs.cpp +++ b/alg/gdal_crs.cpp @@ -102,9 +102,10 @@ struct GCPTransformInfo volatile int nRefCount{}; }; +static CPLXMLNode *GDALSerializeGCPTransformer(void *pTransformArg); + CPL_C_START -CPLXMLNode *GDALSerializeGCPTransformer(void *pTransformArg); -void *GDALDeserializeGCPTransformer(CPLXMLNode *psTree); +void *GDALDeserializeGCPTransformer(const CPLXMLNode *psTree); CPL_C_END /* crs.c */ @@ -501,7 +502,7 @@ CPLXMLNode *GDALSerializeGCPTransformer(void *pTransformArg) /* GDALDeserializeReprojectionTransformer() */ /************************************************************************/ -void *GDALDeserializeGCPTransformer(CPLXMLNode *psTree) +void *GDALDeserializeGCPTransformer(const CPLXMLNode *psTree) { std::vector asGCPs; @@ -515,7 +516,7 @@ void *GDALDeserializeGCPTransformer(CPLXMLNode *psTree) /* -------------------------------------------------------------------- */ /* Check for GCPs. */ /* -------------------------------------------------------------------- */ - CPLXMLNode *psGCPList = CPLGetXMLNode(psTree, "GCPList"); + const CPLXMLNode *psGCPList = CPLGetXMLNode(psTree, "GCPList"); if (psGCPList != nullptr) { diff --git a/alg/gdal_rpc.cpp b/alg/gdal_rpc.cpp index c84178295091..740e8e9da565 100644 --- a/alg/gdal_rpc.cpp +++ b/alg/gdal_rpc.cpp @@ -59,9 +59,10 @@ // #define DEBUG_VERBOSE_EXTRACT_DEM -CPL_C_START CPLXMLNode *GDALSerializeRPCTransformer(void *pTransformArg); -void *GDALDeserializeRPCTransformer(CPLXMLNode *psTree); + +CPL_C_START +void *GDALDeserializeRPCTransformer(const CPLXMLNode *psTree); CPL_C_END constexpr int MAX_ABS_VALUE_WARNINGS = 20; @@ -2326,7 +2327,7 @@ CPLXMLNode *GDALSerializeRPCTransformer(void *pTransformArg) /* GDALDeserializeRPCTransformer() */ /************************************************************************/ -void *GDALDeserializeRPCTransformer(CPLXMLNode *psTree) +void *GDALDeserializeRPCTransformer(const CPLXMLNode *psTree) { char **papszOptions = nullptr; @@ -2334,14 +2335,14 @@ void *GDALDeserializeRPCTransformer(CPLXMLNode *psTree) /* -------------------------------------------------------------------- */ /* Collect metadata. */ /* -------------------------------------------------------------------- */ - CPLXMLNode *psMetadata = CPLGetXMLNode(psTree, "Metadata"); + const CPLXMLNode *psMetadata = CPLGetXMLNode(psTree, "Metadata"); if (psMetadata == nullptr || psMetadata->eType != CXT_Element || !EQUAL(psMetadata->pszValue, "Metadata")) return nullptr; char **papszMD = nullptr; - for (CPLXMLNode *psMDI = psMetadata->psChild; psMDI != nullptr; + for (const CPLXMLNode *psMDI = psMetadata->psChild; psMDI != nullptr; psMDI = psMDI->psNext) { if (!EQUAL(psMDI->pszValue, "MDI") || psMDI->eType != CXT_Element || diff --git a/alg/gdal_tps.cpp b/alg/gdal_tps.cpp index be7159bc50aa..90623671641d 100644 --- a/alg/gdal_tps.cpp +++ b/alg/gdal_tps.cpp @@ -47,9 +47,10 @@ #include "gdal_priv.h" #include "gdalgenericinverse.h" +static CPLXMLNode *GDALSerializeTPSTransformer(void *pTransformArg); + CPL_C_START -CPLXMLNode *GDALSerializeTPSTransformer(void *pTransformArg); -void *GDALDeserializeTPSTransformer(CPLXMLNode *psTree); +void *GDALDeserializeTPSTransformer(const CPLXMLNode *psTree); CPL_C_END struct TPSTransformInfo @@ -438,13 +439,13 @@ CPLXMLNode *GDALSerializeTPSTransformer(void *pTransformArg) /* GDALDeserializeTPSTransformer() */ /************************************************************************/ -void *GDALDeserializeTPSTransformer(CPLXMLNode *psTree) +void *GDALDeserializeTPSTransformer(const CPLXMLNode *psTree) { /* -------------------------------------------------------------------- */ /* Check for GCPs. */ /* -------------------------------------------------------------------- */ - CPLXMLNode *psGCPList = CPLGetXMLNode(psTree, "GCPList"); + const CPLXMLNode *psGCPList = CPLGetXMLNode(psTree, "GCPList"); std::vector asGCPs; if (psGCPList != nullptr) diff --git a/alg/gdalgeoloc.cpp b/alg/gdalgeoloc.cpp index d17d2788098b..7d67b5bcdc31 100644 --- a/alg/gdalgeoloc.cpp +++ b/alg/gdalgeoloc.cpp @@ -69,9 +69,10 @@ constexpr float INVALID_BMXY = -10.0f; #warning "Remove me before committing" #endif +static CPLXMLNode *GDALSerializeGeoLocTransformer(void *pTransformArg); + CPL_C_START -CPLXMLNode *GDALSerializeGeoLocTransformer(void *pTransformArg); -void *GDALDeserializeGeoLocTransformer(CPLXMLNode *psTree); +void *GDALDeserializeGeoLocTransformer(const CPLXMLNode *psTree); CPL_C_END /************************************************************************/ @@ -2155,13 +2156,13 @@ CPLXMLNode *GDALSerializeGeoLocTransformer(void *pTransformArg) /* GDALDeserializeGeoLocTransformer() */ /************************************************************************/ -void *GDALDeserializeGeoLocTransformer(CPLXMLNode *psTree) +void *GDALDeserializeGeoLocTransformer(const CPLXMLNode *psTree) { /* -------------------------------------------------------------------- */ /* Collect metadata. */ /* -------------------------------------------------------------------- */ - CPLXMLNode *psMetadata = CPLGetXMLNode(psTree, "Metadata"); + const CPLXMLNode *psMetadata = CPLGetXMLNode(psTree, "Metadata"); if (psMetadata == nullptr || psMetadata->eType != CXT_Element || !EQUAL(psMetadata->pszValue, "Metadata")) diff --git a/alg/gdaltransformer.cpp b/alg/gdaltransformer.cpp index bff7c93cc3bc..69e33bd9bf1c 100644 --- a/alg/gdaltransformer.cpp +++ b/alg/gdaltransformer.cpp @@ -42,6 +42,7 @@ #include #include +#include #include #include "cpl_conv.h" @@ -58,23 +59,42 @@ #include "ogr_srs_api.h" CPL_C_START -void *GDALDeserializeGCPTransformer(CPLXMLNode *psTree); -void *GDALDeserializeTPSTransformer(CPLXMLNode *psTree); -void *GDALDeserializeGeoLocTransformer(CPLXMLNode *psTree); -void *GDALDeserializeRPCTransformer(CPLXMLNode *psTree); +void *GDALDeserializeGCPTransformer(const CPLXMLNode *psTree); +void *GDALDeserializeTPSTransformer(const CPLXMLNode *psTree); +void *GDALDeserializeGeoLocTransformer(const CPLXMLNode *psTree); +void *GDALDeserializeRPCTransformer(const CPLXMLNode *psTree); CPL_C_END static CPLXMLNode *GDALSerializeReprojectionTransformer(void *pTransformArg); -static void *GDALDeserializeReprojectionTransformer(CPLXMLNode *psTree); +static void *GDALDeserializeReprojectionTransformer(const CPLXMLNode *psTree); static CPLXMLNode *GDALSerializeGenImgProjTransformer(void *pTransformArg); -static void *GDALDeserializeGenImgProjTransformer(CPLXMLNode *psTree); +static void *GDALDeserializeGenImgProjTransformer(const CPLXMLNode *psTree); static void *GDALCreateApproxTransformer2(GDALTransformerFunc pfnRawTransformer, void *pRawTransformerArg, double dfMaxErrorForward, double dfMaxErrorReverse); +static void GDALRegisterBuiltinTransformersUnderLock(); + +// Global mutex used to protect oMapRegisteredTransformers +static std::recursive_mutex oTransformerRegistrationMutex; + +struct GDALTransformerRegistrationEntry +{ + std::string osKey{}; + int nRegistrationOrder = 0; + GDALTransformerFunc pfnTransformerFunc = nullptr; + GDALTransformerCreateForGenImgTransformer + pfnTransformerCreateForGenImgTransformerFunc = nullptr; + GDALTransformDeserializeFunc pfnDeserializeFunc = nullptr; +}; + +// Map of registered GDAL transformers (builtin and custom). +static std::map> + oMapRegisteredTransformers; + /************************************************************************/ /* GDALIsTransformer() */ /************************************************************************/ @@ -1714,6 +1734,218 @@ static void GDALGCPAntimeridianUnwrap(int nGCPCount, GDAL_GCP *pasGCPList, } } +/************************************************************************/ +/* GDALGCPTransformCreateForGenImgTransformer() */ +/************************************************************************/ + +static void *GDALGCPTransformCreateForGenImgTransformer( + const char *pszMethod, bool /* bIsSrcTransformer */, GDALDatasetH hDS, + char **papszTransformOptions, OGRSpatialReferenceH *phSRS) +{ + const int nOrder = + atoi(CSLFetchNameValueDef(papszTransformOptions, "MAX_GCP_ORDER", "0")); + const bool bGCPUseOK = + CSLFetchBoolean(papszTransformOptions, "GCPS_OK", true); + const int nMinimumGcps = atoi(CSLFetchNameValueDef( + papszTransformOptions, "REFINE_MINIMUM_GCPS", "-1")); + + const char *pszValue = + CSLFetchNameValue(papszTransformOptions, "REFINE_TOLERANCE"); + const bool bRefine = pszValue != nullptr; + const double dfTolerance = pszValue ? CPLAtof(pszValue) : 0.0; + + const auto nGCPCount = GDALGetGCPCount(hDS); + + constexpr const char *pszRecognizedMethod = "GCP_POLYNOMIAL"; + if (pszMethod && EQUAL(pszMethod, pszRecognizedMethod)) + { + // ok + } + else if (pszMethod && !EQUAL(pszMethod, pszRecognizedMethod)) + { + return nullptr; + } + else if (!bGCPUseOK || GDALGetGCPCount(hDS) == 0) + { + return nullptr; + } + + auto pasGCPList = GDALDuplicateGCPs(nGCPCount, GDALGetGCPs(hDS)); + + if (!(*phSRS)) + { + auto hSRS = GDALGetGCPSpatialRef(hDS); + if (hSRS) + *phSRS = OSRClone(hSRS); + } + GDALGCPAntimeridianUnwrap(nGCPCount, pasGCPList, + *phSRS + ? *(OGRSpatialReference::FromHandle(*phSRS)) + : OGRSpatialReference(), + papszTransformOptions); + + void *pTransformArg; + if (bRefine) + { + pTransformArg = GDALCreateGCPRefineTransformer( + nGCPCount, pasGCPList, nOrder, FALSE, dfTolerance, nMinimumGcps); + } + else + { + pTransformArg = + GDALCreateGCPTransformer(nGCPCount, pasGCPList, nOrder, FALSE); + } + + GDALDeinitGCPs(nGCPCount, pasGCPList); + CPLFree(pasGCPList); + + return pTransformArg; +} + +/************************************************************************/ +/* GDALTPSTransformCreateForGenImgTransformer() */ +/************************************************************************/ + +static void *GDALTPSTransformCreateForGenImgTransformer( + const char *pszMethod, bool /* bIsSrcTransformer */, GDALDatasetH hDS, + char **papszTransformOptions, OGRSpatialReferenceH *phSRS) +{ + const int nOrder = + atoi(CSLFetchNameValueDef(papszTransformOptions, "MAX_GCP_ORDER", "0")); + const bool bGCPUseOK = + CSLFetchBoolean(papszTransformOptions, "GCPS_OK", true); + + const auto nGCPCount = GDALGetGCPCount(hDS); + + constexpr const char *pszRecognizedMethod = "GCP_TPS"; + if (pszMethod && !EQUAL(pszMethod, pszRecognizedMethod)) + { + return nullptr; + } + else if (!bGCPUseOK || GDALGetGCPCount(hDS) == 0 || nOrder > 0) + { + return nullptr; + } + + auto pasGCPList = GDALDuplicateGCPs(nGCPCount, GDALGetGCPs(hDS)); + + if (!(*phSRS)) + { + auto hSRS = GDALGetGCPSpatialRef(hDS); + if (hSRS) + *phSRS = OSRClone(hSRS); + } + GDALGCPAntimeridianUnwrap(nGCPCount, pasGCPList, + *phSRS + ? *(OGRSpatialReference::FromHandle(*phSRS)) + : OGRSpatialReference(), + papszTransformOptions); + + void *pTransformArg = GDALCreateTPSTransformerInt( + nGCPCount, pasGCPList, FALSE, papszTransformOptions); + + GDALDeinitGCPs(nGCPCount, pasGCPList); + CPLFree(pasGCPList); + + return pTransformArg; +} + +/************************************************************************/ +/* GDALRPCTransformCreateForGenImgTransformer() */ +/************************************************************************/ + +static void *GDALRPCTransformCreateForGenImgTransformer( + const char *pszMethod, bool /* bIsSrcTransformer */, GDALDatasetH hDS, + char **papszTransformOptions, OGRSpatialReferenceH *phSRS) +{ + constexpr const char *pszRecognizedMethod = "RPC"; + if (pszMethod && !EQUAL(pszMethod, pszRecognizedMethod)) + { + return nullptr; + } + + CSLConstList papszMD = GDALGetMetadata(hDS, "RPC"); + GDALRPCInfoV2 sRPCInfo; + if (!papszMD || !GDALExtractRPCInfoV2(papszMD, &sRPCInfo)) + { + return nullptr; + } + + void *pTransformArg = + GDALCreateRPCTransformerV2(&sRPCInfo, FALSE, 0, papszTransformOptions); + if (pTransformArg && !(*phSRS)) + { + *phSRS = OSRNewSpatialReference(nullptr); + OSRSetFromUserInput(*phSRS, SRS_WKT_WGS84_LAT_LONG); + OSRSetAxisMappingStrategy(*phSRS, OAMS_TRADITIONAL_GIS_ORDER); + } + + return pTransformArg; +} + +/************************************************************************/ +/* GDALGeoLocTransformCreateForGenImgTransformer() */ +/************************************************************************/ + +static void *GDALGeoLocTransformCreateForGenImgTransformer( + const char *pszMethod, bool bIsSrcTransformer, GDALDatasetH hDS, + char **papszTransformOptions, OGRSpatialReferenceH *phSRS) +{ + constexpr const char *pszRecognizedMethod = "GEOLOC_ARRAY"; + if (pszMethod && !EQUAL(pszMethod, pszRecognizedMethod)) + { + return nullptr; + } + + CSLConstList papszMD = GDALGetMetadata(hDS, "GEOLOCATION"); + const char *pszGeolocArray = CSLFetchNameValueDef( + papszTransformOptions, + bIsSrcTransformer ? "SRC_GEOLOC_ARRAY" : "DST_GEOLOC_ARRAY", + bIsSrcTransformer + ? CSLFetchNameValue(papszTransformOptions, "GEOLOC_ARRAY") + : nullptr); + if (!papszMD && !pszGeolocArray) + { + return nullptr; + } + + CPLStringList aosGeolocMD; // keep in this scope + if (pszGeolocArray) + { + if (papszMD) + { + CPLError(CE_Warning, CPLE_AppDefined, + "Both GEOLOCATION metadata domain on the %s dataset " + "and %s transformer option are set. " + "Only using the later.", + bIsSrcTransformer ? "source" : "destination", + bIsSrcTransformer ? "[SRC_]GEOLOC_ARRAY" + : "DST_GEOLOC_ARRAY"); + } + aosGeolocMD = GDALCreateGeolocationMetadata(hDS, pszGeolocArray, + bIsSrcTransformer); + if (aosGeolocMD.empty()) + { + return nullptr; + } + papszMD = aosGeolocMD.List(); + } + + void *pTransformArg = GDALCreateGeoLocTransformerEx( + hDS, papszMD, FALSE, nullptr, papszTransformOptions); + if (pTransformArg && !(*phSRS)) + { + const char *pszSRSFromGeolocMD = CSLFetchNameValue(papszMD, "SRS"); + if (pszSRSFromGeolocMD) + { + *phSRS = OSRNewSpatialReference(nullptr); + OSRSetFromUserInput(*phSRS, pszSRSFromGeolocMD); + OSRSetAxisMappingStrategy(*phSRS, OAMS_TRADITIONAL_GIS_ORDER); + } + } + return pTransformArg; +} + /************************************************************************/ /* GDALCreateGenImgProjTransformer2() */ /************************************************************************/ @@ -1914,36 +2146,51 @@ void *GDALCreateGenImgProjTransformer2(GDALDatasetH hSrcDS, GDALDatasetH hDstDS, char **papszOptions) { - CSLConstList papszMD = nullptr; - GDALRPCInfoV2 sRPCInfo; - const char *pszMethod = CSLFetchNameValue(papszOptions, "SRC_METHOD"); - if (pszMethod == nullptr) - pszMethod = CSLFetchNameValue(papszOptions, "METHOD"); - const char *pszSrcSRS = CSLFetchNameValue(papszOptions, "SRC_SRS"); - const char *pszDstSRS = CSLFetchNameValue(papszOptions, "DST_SRS"); - - const char *pszValue = CSLFetchNameValue(papszOptions, "MAX_GCP_ORDER"); - const int nOrder = pszValue ? atoi(pszValue) : 0; - - pszValue = CSLFetchNameValue(papszOptions, "GCPS_OK"); - const bool bGCPUseOK = pszValue ? CPLTestBool(pszValue) : true; + // Create a local map of the registered transformers to avoid locking the + // oTransformerRegistrationMutex mutex for too long. + std::vector> + oRegisteredTransformers; + const auto GetRegisteredTransformers = [&oRegisteredTransformers]() + -> const std::vector> + & + { + if (!oRegisteredTransformers.empty()) + return oRegisteredTransformers; - pszValue = CSLFetchNameValue(papszOptions, "REFINE_MINIMUM_GCPS"); - const int nMinimumGcps = pszValue ? atoi(pszValue) : -1; + { + std::lock_guard oLock(oTransformerRegistrationMutex); + GDALRegisterBuiltinTransformersUnderLock(); + for (const auto &[osKey, poValue] : oMapRegisteredTransformers) + { + if (poValue->pfnTransformerCreateForGenImgTransformerFunc) + { + CPL_IGNORE_RET_VAL(osKey); + auto poEntry = + std::make_unique(); + *poEntry = *poValue; + oRegisteredTransformers.emplace_back(std::move(poEntry)); + } + } + } + std::sort(oRegisteredTransformers.begin(), + oRegisteredTransformers.end(), + [](const auto &a, const auto &b) + { return a->nRegistrationOrder < b->nRegistrationOrder; }); + return oRegisteredTransformers; + }; - pszValue = CSLFetchNameValue(papszOptions, "REFINE_TOLERANCE"); - const bool bRefine = pszValue != nullptr; - const double dfTolerance = pszValue ? CPLAtof(pszValue) : 0.0; + const char *pszSrcSRS = CSLFetchNameValue(papszOptions, "SRC_SRS"); + const char *pszDstSRS = CSLFetchNameValue(papszOptions, "DST_SRS"); double dfWestLongitudeDeg = 0.0; double dfSouthLatitudeDeg = 0.0; double dfEastLongitudeDeg = 0.0; double dfNorthLatitudeDeg = 0.0; bool bHasAreaOfInterest = false; - pszValue = CSLFetchNameValue(papszOptions, "AREA_OF_INTEREST"); - if (pszValue) + if (const char *pszAreaOfInterest = + CSLFetchNameValue(papszOptions, "AREA_OF_INTEREST")) { - char **papszTokens = CSLTokenizeString2(pszValue, ", ", 0); + char **papszTokens = CSLTokenizeString2(pszAreaOfInterest, ", ", 0); if (CSLCount(papszTokens) == 4) { dfWestLongitudeDeg = CPLAtof(papszTokens[0]); @@ -1983,23 +2230,30 @@ void *GDALCreateGenImgProjTransformer2(GDALDatasetH hSrcDS, GDALDatasetH hDstDS, } } - const char *pszSrcGeolocArray = - CSLFetchNameValueDef(papszOptions, "SRC_GEOLOC_ARRAY", - CSLFetchNameValue(papszOptions, "GEOLOC_ARRAY")); - if (pszMethod == nullptr && pszSrcGeolocArray != nullptr) - pszMethod = "GEOLOC_ARRAY"; - /* -------------------------------------------------------------------- */ /* Initialize the transform info. */ /* -------------------------------------------------------------------- */ - GDALGenImgProjTransformInfo *psInfo = - GDALCreateGenImgProjTransformerInternal(); + std::unique_ptr + psInfo{GDALCreateGenImgProjTransformerInternal(), + GDALDestroyGenImgProjTransformer}; bool bCanUseSrcGeoTransform = false; /* -------------------------------------------------------------------- */ /* Get forward and inverse geotransform for the source image. */ /* -------------------------------------------------------------------- */ + + const char *pszMethod = CSLFetchNameValue(papszOptions, "SRC_METHOD"); + if (pszMethod == nullptr) + pszMethod = CSLFetchNameValue(papszOptions, "METHOD"); + if (pszMethod == nullptr && + (CSLFetchNameValueDef(papszOptions, "SRC_GEOLOC_ARRAY", + CSLFetchNameValue(papszOptions, "GEOLOC_ARRAY")))) + { + pszMethod = "GEOLOC_ARRAY"; + } + if (hSrcDS == nullptr || (pszMethod != nullptr && EQUAL(pszMethod, "NO_GEOTRANSFORM"))) { @@ -2019,7 +2273,6 @@ void *GDALCreateGenImgProjTransformer2(GDALDatasetH hSrcDS, GDALDatasetH hDstDS, psInfo->adfSrcInvGeoTransform)) { CPLError(CE_Failure, CPLE_AppDefined, "Cannot invert geotransform"); - GDALDestroyGenImgProjTransformer(psInfo); return nullptr; } if (pszSrcSRS == nullptr) @@ -2038,161 +2291,62 @@ void *GDALCreateGenImgProjTransformer2(GDALDatasetH hSrcDS, GDALDatasetH hDstDS, } bCanUseSrcGeoTransform = true; } - else if (bGCPUseOK && - (pszMethod == nullptr || EQUAL(pszMethod, "GCP_POLYNOMIAL")) && - GDALGetGCPCount(hSrcDS) > 0 && nOrder >= 0) + else { - if (pszSrcSRS == nullptr) - { - auto hSRS = GDALGetGCPSpatialRef(hSrcDS); - if (hSRS) - oSrcSRS = *(OGRSpatialReference::FromHandle(hSRS)); - } - - const auto nGCPCount = GDALGetGCPCount(hSrcDS); - auto pasGCPList = GDALDuplicateGCPs(nGCPCount, GDALGetGCPs(hSrcDS)); - GDALGCPAntimeridianUnwrap(nGCPCount, pasGCPList, oSrcSRS, papszOptions); - - if (bRefine) + OGRSpatialReferenceH hSRS = nullptr; + if (!oSrcSRS.IsEmpty()) { - psInfo->pSrcTransformArg = GDALCreateGCPRefineTransformer( - nGCPCount, pasGCPList, nOrder, FALSE, dfTolerance, - nMinimumGcps); + hSRS = OSRClone(OGRSpatialReference::ToHandle(&oSrcSRS)); } - else + for (const auto &poEntry : GetRegisteredTransformers()) { + CPLAssert(poEntry->pfnTransformerCreateForGenImgTransformerFunc); psInfo->pSrcTransformArg = - GDALCreateGCPTransformer(nGCPCount, pasGCPList, nOrder, FALSE); - } - - GDALDeinitGCPs(nGCPCount, pasGCPList); - CPLFree(pasGCPList); - - if (psInfo->pSrcTransformArg == nullptr) - { - GDALDestroyGenImgProjTransformer(psInfo); - return nullptr; - } - psInfo->pSrcTransformer = GDALGCPTransform; - } - - else if (bGCPUseOK && GDALGetGCPCount(hSrcDS) > 0 && nOrder <= 0 && - (pszMethod == nullptr || EQUAL(pszMethod, "GCP_TPS"))) - { - if (pszSrcSRS == nullptr) - { - auto hSRS = GDALGetGCPSpatialRef(hSrcDS); - if (hSRS) - oSrcSRS = *(OGRSpatialReference::FromHandle(hSRS)); - } - - const auto nGCPCount = GDALGetGCPCount(hSrcDS); - auto pasGCPList = GDALDuplicateGCPs(nGCPCount, GDALGetGCPs(hSrcDS)); - GDALGCPAntimeridianUnwrap(nGCPCount, pasGCPList, oSrcSRS, papszOptions); - - psInfo->pSrcTransformArg = GDALCreateTPSTransformerInt( - nGCPCount, pasGCPList, FALSE, papszOptions); - - GDALDeinitGCPs(nGCPCount, pasGCPList); - CPLFree(pasGCPList); - - if (psInfo->pSrcTransformArg == nullptr) - { - GDALDestroyGenImgProjTransformer(psInfo); - return nullptr; - } - psInfo->pSrcTransformer = GDALTPSTransform; - } - - else if ((pszMethod == nullptr || EQUAL(pszMethod, "RPC")) && - (papszMD = GDALGetMetadata(hSrcDS, "RPC")) != nullptr && - GDALExtractRPCInfoV2(papszMD, &sRPCInfo)) - { - psInfo->pSrcTransformArg = - GDALCreateRPCTransformerV2(&sRPCInfo, FALSE, 0, papszOptions); - if (psInfo->pSrcTransformArg == nullptr) - { - GDALDestroyGenImgProjTransformer(psInfo); - return nullptr; + poEntry->pfnTransformerCreateForGenImgTransformerFunc( + pszMethod, /* bIsSrcTransformer = */ true, hSrcDS, + papszOptions, &hSRS); + if (psInfo->pSrcTransformArg) + { + if (!pszMethod) + { + CPLDebug("WARP", "Using source %s transformer", + poEntry->osKey.c_str()); + } + psInfo->pSrcTransformer = poEntry->pfnTransformerFunc; + break; + } } - psInfo->pSrcTransformer = GDALRPCTransform; - if (pszSrcSRS == nullptr) + if (hSRS) { - oSrcSRS.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG); - oSrcSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); + oSrcSRS = *(OGRSpatialReference::FromHandle(hSRS)); + OSRRelease(hSRS); } - } - else if ((pszMethod == nullptr || EQUAL(pszMethod, "GEOLOC_ARRAY")) && - ((papszMD = GDALGetMetadata(hSrcDS, "GEOLOCATION")) != nullptr || - pszSrcGeolocArray != nullptr)) - { - CPLStringList aosGeolocMD; // keep in this scope - if (pszSrcGeolocArray != nullptr) + if (!psInfo->pSrcTransformArg) { - if (papszMD != nullptr) + if (pszMethod) { - CPLError( - CE_Warning, CPLE_AppDefined, - "Both GEOLOCATION metadata domain on the source dataset " - "and [SRC_]GEOLOC_ARRAY transformer option are set. " - "Only using the later."); - } - aosGeolocMD = - GDALCreateGeolocationMetadata(hSrcDS, pszSrcGeolocArray, - /* bIsSource= */ true); - if (aosGeolocMD.empty()) - { - GDALDestroyGenImgProjTransformer(psInfo); - return nullptr; + CPLError(CE_Failure, CPLE_AppDefined, + "Unable to compute a %s based transformation between " + "pixel/line and georeferenced coordinates for %s.", + pszMethod, GDALGetDescription(hSrcDS)); } - papszMD = aosGeolocMD.List(); - } - psInfo->pSrcTransformArg = GDALCreateGeoLocTransformerEx( - hSrcDS, papszMD, FALSE, nullptr, papszOptions); - if (psInfo->pSrcTransformArg == nullptr) - { - GDALDestroyGenImgProjTransformer(psInfo); - return nullptr; - } - psInfo->pSrcTransformer = GDALGeoLocTransform; - if (pszSrcSRS == nullptr) - { - pszSrcSRS = CSLFetchNameValue(papszMD, "SRS"); - if (pszSrcSRS) + else { - oSrcSRS.SetFromUserInput(pszSrcSRS); - oSrcSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); + CPLError( + CE_Failure, CPLE_AppDefined, + "Unable to compute a transformation between pixel/line " + "and georeferenced coordinates for %s. " + "There is no affine transformation and no GCPs. " + "Specify transformation option SRC_METHOD=NO_GEOTRANSFORM " + "to bypass this check.", + GDALGetDescription(hSrcDS)); } + return nullptr; } } - else if (pszMethod != nullptr) - { - CPLError(CE_Failure, CPLE_AppDefined, - "Unable to compute a %s based transformation between " - "pixel/line and georeferenced coordinates for %s.", - pszMethod, GDALGetDescription(hSrcDS)); - - GDALDestroyGenImgProjTransformer(psInfo); - return nullptr; - } - - else - { - CPLError(CE_Failure, CPLE_AppDefined, - "Unable to compute a transformation between pixel/line " - "and georeferenced coordinates for %s. " - "There is no affine transformation and no GCPs. " - "Specify transformation option SRC_METHOD=NO_GEOTRANSFORM " - "to bypass this check.", - GDALGetDescription(hSrcDS)); - - GDALDestroyGenImgProjTransformer(psInfo); - return nullptr; - } - /* -------------------------------------------------------------------- */ /* Handle optional source approximation transformer. */ /* -------------------------------------------------------------------- */ @@ -2210,7 +2364,6 @@ void *GDALCreateGenImgProjTransformer2(GDALDatasetH hSrcDS, GDALDatasetH hDstDS, CPLAtof(pszSrcApproxErrorReverse)); if (pArg == nullptr) { - GDALDestroyGenImgProjTransformer(psInfo); return nullptr; } psInfo->pSrcTransformArg = pArg; @@ -2225,9 +2378,8 @@ void *GDALCreateGenImgProjTransformer2(GDALDatasetH hSrcDS, GDALDatasetH hDstDS, /* If we have no destination use a unit transform. */ /* -------------------------------------------------------------------- */ const char *pszDstMethod = CSLFetchNameValue(papszOptions, "DST_METHOD"); - const char *pszDstGeolocArray = - CSLFetchNameValue(papszOptions, "DST_GEOLOC_ARRAY"); - if (pszDstMethod == nullptr && pszDstGeolocArray != nullptr) + if (pszDstMethod == nullptr && + CSLFetchNameValue(papszOptions, "DST_GEOLOC_ARRAY")) pszDstMethod = "GEOLOC_ARRAY"; if (!hDstDS || @@ -2255,150 +2407,64 @@ void *GDALCreateGenImgProjTransformer2(GDALDatasetH hSrcDS, GDALDatasetH hDstDS, psInfo->adfDstInvGeoTransform)) { CPLError(CE_Failure, CPLE_AppDefined, "Cannot invert geotransform"); - GDALDestroyGenImgProjTransformer(psInfo); return nullptr; } } - else if (bGCPUseOK && - (pszDstMethod == nullptr || - EQUAL(pszDstMethod, "GCP_POLYNOMIAL")) && - GDALGetGCPCount(hDstDS) > 0 && nOrder >= 0) + else { - if (pszDstSRS == nullptr) - { - auto hSRS = GDALGetGCPSpatialRef(hDstDS); - if (hSRS) - oDstSRS = *(OGRSpatialReference::FromHandle(hSRS)); - } - - const auto nGCPCount = GDALGetGCPCount(hDstDS); - auto pasGCPList = GDALDuplicateGCPs(nGCPCount, GDALGetGCPs(hDstDS)); - GDALGCPAntimeridianUnwrap(nGCPCount, pasGCPList, oDstSRS, papszOptions); - - if (bRefine) + OGRSpatialReferenceH hSRS = nullptr; + if (!oDstSRS.IsEmpty()) { - psInfo->pDstTransformArg = GDALCreateGCPRefineTransformer( - nGCPCount, pasGCPList, nOrder, FALSE, dfTolerance, - nMinimumGcps); + hSRS = OSRClone(OGRSpatialReference::ToHandle(&oDstSRS)); } - else + for (const auto &poEntry : GetRegisteredTransformers()) { + CPLAssert(poEntry->pfnTransformerCreateForGenImgTransformerFunc); psInfo->pDstTransformArg = - GDALCreateGCPTransformer(nGCPCount, pasGCPList, nOrder, FALSE); - } - - GDALDeinitGCPs(nGCPCount, pasGCPList); - CPLFree(pasGCPList); - - if (psInfo->pDstTransformArg == nullptr) - { - GDALDestroyGenImgProjTransformer(psInfo); - return nullptr; + poEntry->pfnTransformerCreateForGenImgTransformerFunc( + pszDstMethod, /* bIsSrcTransformer = */ false, hDstDS, + papszOptions, &hSRS); + if (psInfo->pDstTransformArg) + { + if (!pszDstMethod) + { + CPLDebug("WARP", "Using target %s transformer", + poEntry->osKey.c_str()); + } + psInfo->pDstTransformer = poEntry->pfnTransformerFunc; + break; + } } - psInfo->pDstTransformer = GDALGCPTransform; - } - else if (bGCPUseOK && GDALGetGCPCount(hDstDS) > 0 && nOrder <= 0 && - (pszDstMethod == nullptr || EQUAL(pszDstMethod, "GCP_TPS"))) - { - if (pszDstSRS == nullptr) + if (hSRS) { - auto hSRS = GDALGetGCPSpatialRef(hDstDS); - if (hSRS) - oDstSRS = *(OGRSpatialReference::FromHandle(hSRS)); + oDstSRS = *(OGRSpatialReference::FromHandle(hSRS)); + OSRRelease(hSRS); } - const auto nGCPCount = GDALGetGCPCount(hDstDS); - auto pasGCPList = GDALDuplicateGCPs(nGCPCount, GDALGetGCPs(hDstDS)); - GDALGCPAntimeridianUnwrap(nGCPCount, pasGCPList, oDstSRS, papszOptions); - - psInfo->pDstTransformArg = GDALCreateTPSTransformerInt( - nGCPCount, pasGCPList, FALSE, papszOptions); - - GDALDeinitGCPs(nGCPCount, pasGCPList); - CPLFree(pasGCPList); - - if (psInfo->pDstTransformArg == nullptr) + if (!psInfo->pDstTransformArg) { - GDALDestroyGenImgProjTransformer(psInfo); - return nullptr; - } - psInfo->pDstTransformer = GDALTPSTransform; - } - else if ((pszDstMethod == nullptr || EQUAL(pszDstMethod, "RPC")) && - (papszMD = GDALGetMetadata(hDstDS, "RPC")) != nullptr && - GDALExtractRPCInfoV2(papszMD, &sRPCInfo)) - { - psInfo->pDstTransformArg = - GDALCreateRPCTransformerV2(&sRPCInfo, FALSE, 0, papszOptions); - if (psInfo->pDstTransformArg == nullptr) - { - GDALDestroyGenImgProjTransformer(psInfo); - return nullptr; - } - psInfo->pDstTransformer = GDALRPCTransform; - if (pszDstSRS == nullptr) - { - oDstSRS.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG); - oDstSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); - } - } - else if ((pszDstMethod == nullptr || EQUAL(pszDstMethod, "GEOLOC_ARRAY")) && - ((papszMD = GDALGetMetadata(hDstDS, "GEOLOCATION")) != nullptr || - pszDstGeolocArray != nullptr)) - { - CPLStringList aosGeolocMD; // keep in this scope - if (pszDstGeolocArray != nullptr) - { - if (papszMD != nullptr) + if (pszDstMethod) { - CPLError( - CE_Warning, CPLE_AppDefined, - "Both GEOLOCATION metadata domain on the target dataset " - "and DST_GEOLOC_ARRAY transformer option are set. " - "Only using the later."); - } - aosGeolocMD = - GDALCreateGeolocationMetadata(hDstDS, pszDstGeolocArray, - /* bIsSource= */ false); - if (aosGeolocMD.empty()) - { - GDALDestroyGenImgProjTransformer(psInfo); - return nullptr; + CPLError(CE_Failure, CPLE_AppDefined, + "Unable to compute a %s based transformation between " + "pixel/line and georeferenced coordinates for %s.", + pszDstMethod, GDALGetDescription(hDstDS)); } - papszMD = aosGeolocMD.List(); - } - psInfo->pDstTransformArg = GDALCreateGeoLocTransformerEx( - hDstDS, papszMD, FALSE, nullptr, papszOptions); - if (psInfo->pDstTransformArg == nullptr) - { - GDALDestroyGenImgProjTransformer(psInfo); - return nullptr; - } - psInfo->pDstTransformer = GDALGeoLocTransform; - if (pszDstSRS == nullptr) - { - pszDstSRS = CSLFetchNameValue(papszMD, "SRS"); - if (pszDstSRS) + else { - oDstSRS.SetFromUserInput(pszDstSRS); - oDstSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); + CPLError( + CE_Failure, CPLE_AppDefined, + "Unable to compute a transformation between pixel/line " + "and georeferenced coordinates for %s. " + "There is no affine transformation and no GCPs. " + "Specify transformation option DST_METHOD=NO_GEOTRANSFORM " + "to bypass this check.", + GDALGetDescription(hDstDS)); } + return nullptr; } } - else - { - CPLError(CE_Failure, CPLE_AppDefined, - "Unable to compute a transformation between pixel/line " - "and georeferenced coordinates for %s. " - "There is no affine transformation and no GCPs. " - "Specify transformation option DST_METHOD=NO_GEOTRANSFORM " - "to bypass this check.", - GDALGetDescription(hDstDS)); - - GDALDestroyGenImgProjTransformer(psInfo); - return nullptr; - } /* -------------------------------------------------------------------- */ /* Handle optional destination approximation transformer. */ @@ -2417,7 +2483,6 @@ void *GDALCreateGenImgProjTransformer2(GDALDatasetH hSrcDS, GDALDatasetH hDstDS, CPLAtof(pszDstApproxErrorReverse)); if (pArg == nullptr) { - GDALDestroyGenImgProjTransformer(psInfo); return nullptr; } psInfo->pDstTransformArg = pArg; @@ -2515,7 +2580,6 @@ void *GDALCreateGenImgProjTransformer2(GDALDatasetH hSrcDS, GDALDatasetH hDstDS, if (psInfo->pReprojectArg == nullptr) { - GDALDestroyGenImgProjTransformer(psInfo); return nullptr; } psInfo->pReproject = GDALReprojectionTransform; @@ -2536,7 +2600,6 @@ void *GDALCreateGenImgProjTransformer2(GDALDatasetH hSrcDS, GDALDatasetH hDstDS, CPLAtof(psApproxErrorFwd), CPLAtof(psApproxErrorReverse)); if (pArg == nullptr) { - GDALDestroyGenImgProjTransformer(psInfo); return nullptr; } psInfo->pReprojectArg = pArg; @@ -2546,7 +2609,7 @@ void *GDALCreateGenImgProjTransformer2(GDALDatasetH hSrcDS, GDALDatasetH hDstDS, } } - return psInfo; + return psInfo.release(); } /************************************************************************/ @@ -3171,7 +3234,7 @@ static void GDALDeserializeGeoTransform(const char *pszGT, /* GDALDeserializeGenImgProjTransformer() */ /************************************************************************/ -void *GDALDeserializeGenImgProjTransformer(CPLXMLNode *psTree) +void *GDALDeserializeGenImgProjTransformer(const CPLXMLNode *psTree) { /* -------------------------------------------------------------------- */ @@ -3273,7 +3336,7 @@ void *GDALDeserializeGenImgProjTransformer(CPLXMLNode *psTree) /* -------------------------------------------------------------------- */ /* Reproject transformer */ /* -------------------------------------------------------------------- */ - CPLXMLNode *psSubtree = CPLGetXMLNode(psTree, "ReprojectTransformer"); + const CPLXMLNode *psSubtree = CPLGetXMLNode(psTree, "ReprojectTransformer"); if (psSubtree != nullptr && psSubtree->psChild != nullptr) { GDALDeserializeTransformer(psSubtree->psChild, &psInfo->pReproject, @@ -3638,7 +3701,7 @@ static CPLXMLNode *GDALSerializeReprojectionTransformer(void *pTransformArg) /* GDALDeserializeReprojectionTransformer() */ /************************************************************************/ -static void *GDALDeserializeReprojectionTransformer(CPLXMLNode *psTree) +static void *GDALDeserializeReprojectionTransformer(const CPLXMLNode *psTree) { const char *pszSourceSRS = CPLGetXMLValue(psTree, "SourceSRS", nullptr); @@ -4250,7 +4313,7 @@ int GDALApproxTransform(void *pCBData, int bDstToSrc, int nPoints, double *x, /* GDALDeserializeApproxTransformer() */ /************************************************************************/ -static void *GDALDeserializeApproxTransformer(CPLXMLNode *psTree) +static void *GDALDeserializeApproxTransformer(const CPLXMLNode *psTree) { double dfMaxErrorForward = 0.25; @@ -4277,7 +4340,7 @@ static void *GDALDeserializeApproxTransformer(CPLXMLNode *psTree) GDALTransformerFunc pfnBaseTransform = nullptr; void *pBaseCBData = nullptr; - CPLXMLNode *psContainer = CPLGetXMLNode(psTree, "BaseTransformer"); + const CPLXMLNode *psContainer = CPLGetXMLNode(psTree, "BaseTransformer"); if (psContainer != nullptr && psContainer->psChild != nullptr) { @@ -4449,75 +4512,153 @@ CPLXMLNode *GDALSerializeTransformer(GDALTransformerFunc /* pfnFunc */, } /************************************************************************/ -/* GDALRegisterTransformDeserializer() */ +/* GDALRegisterTransformer() */ /************************************************************************/ -static CPLList *psListDeserializer = nullptr; -static CPLMutex *hDeserializerMutex = nullptr; - -typedef struct -{ - char *pszTransformName; - GDALTransformerFunc pfnTransformerFunc; - GDALTransformDeserializeFunc pfnDeserializeFunc; -} TransformDeserializerInfo; - -void *GDALRegisterTransformDeserializer( - const char *pszTransformName, GDALTransformerFunc pfnTransformerFunc, - GDALTransformDeserializeFunc pfnDeserializeFunc) +/** Registers a new spatial point transformer. + * + * The transformer instance returned by pfnDeserializeFunc() or + * pfnTransformerCreateForGenImgTransformerFunc() should be a structure whose + * first member is an instance of GDALTransformerInfo. + * + * @param pszTransformName Transformer name. Must not be NULL. It must be + * unique as used as a key (case insensitive) + * The following keys are reserved as GDAL builtins: + * "GenImgProjTransformer", "ReprojectionTransformer", + * "GCPTransformer", "TPSTransformer", "GeoLocTransformer", + * "RPCTransformer", "ApproxTransformer". + * This is used by GDALDeserializeTransformer() to + * determine which transformer is appropriate to + * instanciate a transformer from its XML serialization, + * and must thus be a valid XML element name (possibly + * namespaced) + * @param pfnTransformerFunc Transformation function. Must not be NULL. + * It must also be the same value as the + * GDALTransformerInfo::pfnTransform member of the + * transformer instances returned by pfnDeserializeFunc + * and pfnTransformerCreateForGenImgTransformerFunc. + * @param pfnDeserializeFunc XML deserizaliation function. For example used to + * instantiate a transformer instance from a warped VRT. + * @param pfnTransformerCreateForGenImgTransformerFunc Function to instantiate + * a transformer for use by GDALGenImgProjTransform() (typically for gdalwarp). + * If NULL, the transformer cannot be use by the GDALCreateGenImgProjTransformer() + * family of functions. + * @since GDAL 3.10 + * @return an opaque pointer that can be passed to GDALUnregisterTransformer(), + * or NULL in case of error. + */ +void *GDALRegisterTransformer(const char *pszTransformName, + GDALTransformerFunc pfnTransformerFunc, + GDALTransformDeserializeFunc pfnDeserializeFunc, + GDALTransformerCreateForGenImgTransformer + pfnTransformerCreateForGenImgTransformerFunc) { - TransformDeserializerInfo *psInfo = - static_cast( - CPLMalloc(sizeof(TransformDeserializerInfo))); - psInfo->pszTransformName = CPLStrdup(pszTransformName); - psInfo->pfnTransformerFunc = pfnTransformerFunc; - psInfo->pfnDeserializeFunc = pfnDeserializeFunc; + VALIDATE_POINTER1(pszTransformName, __func__, nullptr); + VALIDATE_POINTER1(pfnTransformerFunc, __func__, nullptr); - CPLMutexHolderD(&hDeserializerMutex); - psListDeserializer = CPLListInsert(psListDeserializer, psInfo, 0); + std::lock_guard oLock(oTransformerRegistrationMutex); - return psInfo; + const std::string osKey = CPLString(pszTransformName).toupper(); + + if (oMapRegisteredTransformers.find(osKey) != + oMapRegisteredTransformers.end()) + { + CPLError(CE_Failure, CPLE_AppDefined, + "GDALRegisterTransformer() already called for " + "pszTransformName = '%s'", + pszTransformName); + return nullptr; + } + + auto poRegistrationEntry = + std::make_unique(); + poRegistrationEntry->osKey = osKey; + poRegistrationEntry->nRegistrationOrder = + static_cast(oMapRegisteredTransformers.size()); + poRegistrationEntry->pfnTransformerFunc = pfnTransformerFunc; + poRegistrationEntry->pfnTransformerCreateForGenImgTransformerFunc = + pfnTransformerCreateForGenImgTransformerFunc; + poRegistrationEntry->pfnDeserializeFunc = pfnDeserializeFunc; + + oMapRegisteredTransformers[osKey] = std::move(poRegistrationEntry); + return oMapRegisteredTransformers[osKey].get(); } /************************************************************************/ -/* GDALUnregisterTransformDeserializer() */ +/* GDALUnregisterTransformer() */ /************************************************************************/ -void GDALUnregisterTransformDeserializer(void *pData) +/** Unregisters a spatial point transformer previously registered with + * GDALRegisterTransformer(). + * + * @param pData Value returned by GDALRegisterTransformer() + * @since GDAL 3.10 + */ +void GDALUnregisterTransformer(void *pData) { - CPLMutexHolderD(&hDeserializerMutex); - CPLList *psList = psListDeserializer; - CPLList *psLast = nullptr; - while (psList) + std::lock_guard oLock(oTransformerRegistrationMutex); + + if (pData) { - if (psList->pData == pData) - { - TransformDeserializerInfo *psInfo = - static_cast(pData); - CPLFree(psInfo->pszTransformName); - CPLFree(pData); - if (psLast) - psLast->psNext = psList->psNext; - else - psListDeserializer = nullptr; - CPLFree(psList); - break; - } - psLast = psList; - psList = psList->psNext; + GDALTransformerRegistrationEntry *psEntry = + static_cast(pData); + oMapRegisteredTransformers.erase(psEntry->osKey); } } /************************************************************************/ -/* GDALUnregisterTransformDeserializer() */ +/* GDALCleanupTransformers() */ +/************************************************************************/ + +/** Called by GDALDestroyDriverManager */ +void GDALCleanupTransformers() +{ + std::lock_guard oLock(oTransformerRegistrationMutex); + + oMapRegisteredTransformers.clear(); +} + +/************************************************************************/ +/* GDALRegisterBuiltinTransformersUnderLock() */ /************************************************************************/ -void GDALCleanupTransformDeserializerMutex() +/** Register built-in transfomers. + * + * Must be called under oTransformerRegistrationMutex locked. + */ +void GDALRegisterBuiltinTransformersUnderLock() { - if (hDeserializerMutex != nullptr) + if (oMapRegisteredTransformers.empty()) { - CPLDestroyMutex(hDeserializerMutex); - hDeserializerMutex = nullptr; + // Transformers that can be used as source/target transformer for GDALGenImgProjTransform + GDALRegisterTransformer("GCPTransformer", GDALGCPTransform, + GDALDeserializeGCPTransformer, + GDALGCPTransformCreateForGenImgTransformer); + + GDALRegisterTransformer("TPSTransformer", GDALTPSTransform, + GDALDeserializeTPSTransformer, + GDALTPSTransformCreateForGenImgTransformer); + + GDALRegisterTransformer("RPCTransformer", GDALRPCTransform, + GDALDeserializeRPCTransformer, + GDALRPCTransformCreateForGenImgTransformer); + + GDALRegisterTransformer("GeoLocTransformer", GDALGeoLocTransform, + GDALDeserializeGeoLocTransformer, + GDALGeoLocTransformCreateForGenImgTransformer); + + // Below ones cannot be used as source/target transformer for GDALGenImgProjTransform + + GDALRegisterTransformer("GenImgProjTransformer", + GDALGenImgProjTransform, + GDALDeserializeGenImgProjTransformer, nullptr); + + GDALRegisterTransformer( + "ReprojectionTransformer", GDALReprojectionTransform, + GDALDeserializeReprojectionTransformer, nullptr); + + GDALRegisterTransformer("ApproxTransformer", GDALApproxTransform, + GDALDeserializeApproxTransformer, nullptr); } } @@ -4533,78 +4674,42 @@ CPLErr GDALDeserializeTransformer(CPLXMLNode *psTree, *ppfnFunc = nullptr; *ppTransformArg = nullptr; - CPLErrorReset(); - if (psTree == nullptr || psTree->eType != CXT_Element) + { CPLError(CE_Failure, CPLE_AppDefined, "Malformed element in GDALDeserializeTransformer"); - else if (EQUAL(psTree->pszValue, "GenImgProjTransformer")) - { - *ppfnFunc = GDALGenImgProjTransform; - *ppTransformArg = GDALDeserializeGenImgProjTransformer(psTree); - } - else if (EQUAL(psTree->pszValue, "ReprojectionTransformer")) - { - *ppfnFunc = GDALReprojectionTransform; - *ppTransformArg = GDALDeserializeReprojectionTransformer(psTree); - } - else if (EQUAL(psTree->pszValue, "GCPTransformer")) - { - *ppfnFunc = GDALGCPTransform; - *ppTransformArg = GDALDeserializeGCPTransformer(psTree); - } - else if (EQUAL(psTree->pszValue, "TPSTransformer")) - { - *ppfnFunc = GDALTPSTransform; - *ppTransformArg = GDALDeserializeTPSTransformer(psTree); - } - else if (EQUAL(psTree->pszValue, "GeoLocTransformer")) - { - *ppfnFunc = GDALGeoLocTransform; - *ppTransformArg = GDALDeserializeGeoLocTransformer(psTree); - } - else if (EQUAL(psTree->pszValue, "RPCTransformer")) - { - *ppfnFunc = GDALRPCTransform; - *ppTransformArg = GDALDeserializeRPCTransformer(psTree); - } - else if (EQUAL(psTree->pszValue, "ApproxTransformer")) - { - *ppfnFunc = GDALApproxTransform; - *ppTransformArg = GDALDeserializeApproxTransformer(psTree); + return CE_Failure; } - else - { - GDALTransformDeserializeFunc pfnDeserializeFunc = nullptr; - { - CPLMutexHolderD(&hDeserializerMutex); - CPLList *psList = psListDeserializer; - while (psList) - { - TransformDeserializerInfo *psInfo = - static_cast(psList->pData); - if (strcmp(psInfo->pszTransformName, psTree->pszValue) == 0) - { - *ppfnFunc = psInfo->pfnTransformerFunc; - pfnDeserializeFunc = psInfo->pfnDeserializeFunc; - break; - } - psList = psList->psNext; - } - } - if (pfnDeserializeFunc != nullptr) - { - *ppTransformArg = pfnDeserializeFunc(psTree); - } - else + const char *pszTransformName = psTree->pszValue; + const std::string osKey = CPLString(pszTransformName).toupper(); + GDALTransformDeserializeFunc pfnDeserializeFunc = nullptr; + { + std::lock_guard oLock(oTransformerRegistrationMutex); + GDALRegisterBuiltinTransformersUnderLock(); + auto oIter = oMapRegisteredTransformers.find(osKey); + if (oIter == oMapRegisteredTransformers.end()) { CPLError(CE_Failure, CPLE_AppDefined, - "Unrecognized element '%s' GDALDeserializeTransformer", - psTree->pszValue); + "GDALDeserializeTransformer(): unknown transformer '%s'", + pszTransformName); + return CE_Failure; } + pfnDeserializeFunc = oIter->second->pfnDeserializeFunc; + if (pfnDeserializeFunc) + *ppfnFunc = oIter->second->pfnTransformerFunc; + } + if (!pfnDeserializeFunc) + { + CPLError(CE_Failure, CPLE_AppDefined, + "GDALDeserializeTransformer(): no deserizaliation function " + "registered for '%s'", + pszTransformName); + return CE_Failure; } + CPLErrorReset(); + *ppTransformArg = pfnDeserializeFunc(psTree); return CPLGetLastErrorType(); } diff --git a/gcore/gdaldrivermanager.cpp b/gcore/gdaldrivermanager.cpp index 9fa1b97e6669..be244b783588 100644 --- a/gcore/gdaldrivermanager.cpp +++ b/gcore/gdaldrivermanager.cpp @@ -306,9 +306,9 @@ GDALDriverManager::~GDALDriverManager() GDALRasterBlock::DestroyRBMutex(); /* -------------------------------------------------------------------- */ - /* Cleanup gdaltransformer.cpp mutex. */ + /* Cleanup GDAL transformers and mutex. */ /* -------------------------------------------------------------------- */ - GDALCleanupTransformDeserializerMutex(); + GDALCleanupTransformers(); /* -------------------------------------------------------------------- */ /* Cleanup cpl_error.cpp mutex. */