EVOLUTION-MANAGER
Edit File: ogrct.cpp
/****************************************************************************** * * Project: OpenGIS Simple Features Reference Implementation * Purpose: The OGRSCoordinateTransformation class. * Author: Frank Warmerdam, warmerdam@pobox.com * ****************************************************************************** * Copyright (c) 2000, Frank Warmerdam * Copyright (c) 2008-2013, Even Rouault <even dot rouault at mines-paris dot org> * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #include "cpl_port.h" #include "ogr_spatialref.h" #include <cmath> #include <cstring> #include "cpl_conv.h" #include "cpl_error.h" #include "cpl_multiproc.h" #include "cpl_string.h" #include "ogr_core.h" #include "ogr_srs_api.h" #ifdef PROJ_STATIC #include "proj_api.h" #endif CPL_CVSID("$Id: ogrct.cpp 37166 2017-01-17 16:38:53Z rouault $"); /* ==================================================================== */ /* PROJ.4 interface stuff. */ /* ==================================================================== */ #ifndef PROJ_STATIC #define projPJ void * #define projCtx void * static const double RAD_TO_DEG = 57.29577951308232; static const double DEG_TO_RAD = 0.0174532925199432958; #else #if PJ_VERSION < 480 #define projCtx void * #endif #endif static CPLMutex *hPROJMutex = NULL; static projPJ (*pfn_pj_init_plus)( const char * ) = NULL; static projPJ (*pfn_pj_init)( int, char** ) = NULL; static void (*pfn_pj_free)( projPJ ) = NULL; static int (*pfn_pj_transform)( projPJ, projPJ, long, int, double *, double *, double * ) = NULL; static int *(*pfn_pj_get_errno_ref)( void ) = NULL; static char *(*pfn_pj_strerrno)( int ) = NULL; static char *(*pfn_pj_get_def)( projPJ, int ) = NULL; static void (*pfn_pj_dalloc)( void * ) = NULL; static projPJ (*pfn_pj_init_plus_ctx)( projCtx, const char * ) = NULL; static int (*pfn_pj_ctx_get_errno)( projCtx ) = NULL; static projCtx (*pfn_pj_ctx_alloc)() = NULL; static void (*pfn_pj_ctx_free)( projCtx ) = NULL; // Locale-safe proj starts with 4.10. #if defined(PJ_LOCALE_SAFE) static bool bProjLocaleSafe = PJ_LOCALE_SAFE != 0; #else static bool bProjLocaleSafe = false; #endif #if defined(WIN32) && !defined(__MINGW32__) # define LIBNAME "proj.dll" #elif defined(__MINGW32__) // XXX: If PROJ.4 library was properly built using libtool in Cygwin or MinGW // environments it has the interface version number embedded in the file name // (it is CURRENT-AGE number). If DLL came somewhere else (e.g. from MSVC // build) it can be named either way, so use PROJSO environment variable to // specify the right library name. By default assume that in Cygwin/MinGW all // components were built in the same way. # define LIBNAME "libproj-9.dll" #elif defined(__CYGWIN__) # define LIBNAME "cygproj-9.dll" #elif defined(__APPLE__) # define LIBNAME "libproj.dylib" #else # define LIBNAME "libproj.so" #endif /************************************************************************/ /* OCTCleanupProjMutex() */ /************************************************************************/ void OCTCleanupProjMutex() { if( hPROJMutex != NULL ) { CPLDestroyMutex(hPROJMutex); hPROJMutex = NULL; } } /************************************************************************/ /* OGRProj4CT */ /************************************************************************/ class OGRProj4CT : public OGRCoordinateTransformation { OGRSpatialReference *poSRSSource; void *psPJSource; bool bSourceLatLong; double dfSourceToRadians; bool bSourceWrap; double dfSourceWrapLong; OGRSpatialReference *poSRSTarget; void *psPJTarget; bool bTargetLatLong; double dfTargetFromRadians; bool bTargetWrap; double dfTargetWrapLong; bool bIdentityTransform; bool bWebMercatorToWGS84; int nErrorCount; bool bCheckWithInvertProj; double dfThreshold; projCtx pjctx; int InitializeNoLock( OGRSpatialReference *poSource, OGRSpatialReference *poTarget ); int nMaxCount; double *padfOriX; double *padfOriY; double *padfOriZ; double *padfTargetX; double *padfTargetY; double *padfTargetZ; bool m_bEmitErrors; bool bNoTransform; public: OGRProj4CT(); virtual ~OGRProj4CT(); int Initialize( OGRSpatialReference *poSource, OGRSpatialReference *poTarget ); virtual OGRSpatialReference *GetSourceCS() override; virtual OGRSpatialReference *GetTargetCS() override; virtual int Transform( int nCount, double *x, double *y, double *z = NULL ) override; virtual int TransformEx( int nCount, double *x, double *y, double *z = NULL, int *panSuccess = NULL ) override; // TODO(schwehr): Make GetEmitErrors const. virtual bool GetEmitErrors() override { return m_bEmitErrors; } virtual void SetEmitErrors( bool bEmitErrors ) override { m_bEmitErrors = bEmitErrors; } }; /************************************************************************/ /* GetProjLibraryName() */ /************************************************************************/ static const char* GetProjLibraryName() { const char *pszLibName = LIBNAME; if( CPLGetConfigOption("PROJSO", NULL) != NULL ) pszLibName = CPLGetConfigOption("PROJSO", NULL); return pszLibName; } /************************************************************************/ /* LoadProjLibrary() */ /************************************************************************/ static bool LoadProjLibrary_unlocked() { static bool bTriedToLoad = false; if( bTriedToLoad ) return pfn_pj_transform != NULL; bTriedToLoad = true; const char *pszLibName = GetProjLibraryName(); #ifdef PROJ_STATIC pfn_pj_init = pj_init; pfn_pj_init_plus = pj_init_plus; pfn_pj_free = pj_free; pfn_pj_transform = pj_transform; pfn_pj_get_errno_ref = (int *(*)(void)) pj_get_errno_ref; pfn_pj_strerrno = pj_strerrno; pfn_pj_dalloc = pj_dalloc; #if PJ_VERSION >= 446 pfn_pj_get_def = pj_get_def; #endif #if PJ_VERSION >= 480 pfn_pj_ctx_alloc = pj_ctx_alloc; pfn_pj_ctx_free = pj_ctx_free; pfn_pj_init_plus_ctx = pj_init_plus_ctx; pfn_pj_ctx_get_errno = pj_ctx_get_errno; #endif #else CPLPushErrorHandler( CPLQuietErrorHandler ); // coverity[tainted_string] pfn_pj_init = (projPJ (*)(int, char**)) CPLGetSymbol( pszLibName, "pj_init" ); CPLPopErrorHandler(); if( pfn_pj_init == NULL ) return false; pfn_pj_init_plus = (projPJ (*)(const char *)) CPLGetSymbol( pszLibName, "pj_init_plus" ); pfn_pj_free = (void (*)(projPJ)) CPLGetSymbol( pszLibName, "pj_free" ); pfn_pj_transform = (int (*)(projPJ, projPJ, long, int, double *, double *, double *)) CPLGetSymbol( pszLibName, "pj_transform" ); pfn_pj_get_errno_ref = (int *(*)(void)) CPLGetSymbol( pszLibName, "pj_get_errno_ref" ); pfn_pj_strerrno = (char *(*)(int)) CPLGetSymbol( pszLibName, "pj_strerrno" ); CPLPushErrorHandler( CPLQuietErrorHandler ); pfn_pj_get_def = (char *(*)(projPJ, int)) CPLGetSymbol( pszLibName, "pj_get_def" ); pfn_pj_dalloc = (void (*)(void*)) CPLGetSymbol( pszLibName, "pj_dalloc" ); // PROJ 4.8.0 symbols. pfn_pj_ctx_alloc = (projCtx (*)( void )) CPLGetSymbol( pszLibName, "pj_ctx_alloc" ); pfn_pj_ctx_free = (void (*)( projCtx )) CPLGetSymbol( pszLibName, "pj_ctx_free" ); pfn_pj_init_plus_ctx = (projPJ (*)( projCtx, const char * )) CPLGetSymbol( pszLibName, "pj_init_plus_ctx" ); pfn_pj_ctx_get_errno = (int (*)( projCtx )) CPLGetSymbol( pszLibName, "pj_ctx_get_errno" ); bProjLocaleSafe = CPLGetSymbol(pszLibName, "pj_atof") != NULL; CPLPopErrorHandler(); CPLErrorReset(); #endif if( pfn_pj_ctx_alloc != NULL && pfn_pj_ctx_free != NULL && pfn_pj_init_plus_ctx != NULL && pfn_pj_ctx_get_errno != NULL && CPLTestBool(CPLGetConfigOption("USE_PROJ_480_FEATURES", "YES")) ) { CPLDebug("OGRCT", "PROJ >= 4.8.0 features enabled"); } else { pfn_pj_ctx_alloc = NULL; pfn_pj_ctx_free = NULL; pfn_pj_init_plus_ctx = NULL; pfn_pj_ctx_get_errno = NULL; } if( bProjLocaleSafe ) CPLDebug("OGRCT", "Using locale-safe proj version"); if( pfn_pj_transform == NULL ) { CPLError( CE_Failure, CPLE_AppDefined, "Attempt to load %s, but couldn't find pj_transform. " "Please upgrade to PROJ 4.1.2 or later.", pszLibName ); return false; } return true; } static bool LoadProjLibrary() { CPLMutexHolderD( &hPROJMutex ); return LoadProjLibrary_unlocked(); } /************************************************************************/ /* OCTProj4Normalize() */ /************************************************************************/ /** This function is really just here since we already have all * the code to load libproj.so. It is intended to "normalize" * a proj.4 definition, expanding +init= definitions and so * forth as possible. */ static char *OCTProj4NormalizeInternal( const char *pszProj4Src ) { projPJ psPJSource = pfn_pj_init_plus( pszProj4Src ); if( psPJSource == NULL ) return CPLStrdup( pszProj4Src ); char *pszNewProj4Def = pfn_pj_get_def( psPJSource, 0 ); pfn_pj_free( psPJSource ); if( pszNewProj4Def == NULL ) return CPLStrdup( pszProj4Src ); char *pszCopy = CPLStrdup( pszNewProj4Def ); pfn_pj_dalloc( pszNewProj4Def ); return pszCopy; } char *OCTProj4Normalize( const char *pszProj4Src ) { CPLMutexHolderD( &hPROJMutex ); if( !LoadProjLibrary_unlocked() || pfn_pj_dalloc == NULL || pfn_pj_get_def == NULL ) return CPLStrdup( pszProj4Src ); if( bProjLocaleSafe ) { return OCTProj4NormalizeInternal(pszProj4Src); } else { CPLLocaleC oLocaleEnforcer; return OCTProj4NormalizeInternal(pszProj4Src); } } /************************************************************************/ /* OCTDestroyCoordinateTransformation() */ /************************************************************************/ /** * \brief OGRCoordinateTransformation destructor. * * This function is the same as OGRCoordinateTransformation::DestroyCT() * * @param hCT the object to delete */ void CPL_STDCALL OCTDestroyCoordinateTransformation( OGRCoordinateTransformationH hCT ) { delete (OGRCoordinateTransformation *) hCT; } /************************************************************************/ /* DestroyCT() */ /************************************************************************/ /** * \brief OGRCoordinateTransformation destructor. * * This function is the same as * OGRCoordinateTransformation::~OGRCoordinateTransformation() * and OCTDestroyCoordinateTransformation() * * This static method will destroy a OGRCoordinateTransformation. It is * equivalent to calling delete on the object, but it ensures that the * deallocation is properly executed within the OGR libraries heap on * platforms where this can matter (win32). * * @param poCT the object to delete * * @since GDAL 1.7.0 */ void OGRCoordinateTransformation::DestroyCT( OGRCoordinateTransformation* poCT ) { delete poCT; } /************************************************************************/ /* OGRCreateCoordinateTransformation() */ /************************************************************************/ /** * Create transformation object. * * This is the same as the C function OCTNewCoordinateTransformation(). * * Input spatial reference system objects are assigned * by copy (calling clone() method) and no ownership transfer occurs. * * The delete operator, or OCTDestroyCoordinateTransformation() should * be used to destroy transformation objects. * * The PROJ.4 library must be available at run-time. * * @param poSource source spatial reference system. * @param poTarget target spatial reference system. * @return NULL on failure or a ready to use transformation object. */ OGRCoordinateTransformation* OGRCreateCoordinateTransformation( OGRSpatialReference *poSource, OGRSpatialReference *poTarget ) { if( pfn_pj_init == NULL && !LoadProjLibrary() ) { CPLError( CE_Failure, CPLE_NotSupported, "Unable to load PROJ.4 library (%s), creation of " "OGRCoordinateTransformation failed.", GetProjLibraryName() ); return NULL; } OGRProj4CT *poCT = new OGRProj4CT(); if( !poCT->Initialize( poSource, poTarget ) ) { delete poCT; return NULL; } return poCT; } /************************************************************************/ /* OCTNewCoordinateTransformation() */ /************************************************************************/ /** * Create transformation object. * * This is the same as the C++ function OGRCreateCoordinateTransformation(). * * Input spatial reference system objects are assigned * by copy (calling clone() method) and no ownership transfer occurs. * * OCTDestroyCoordinateTransformation() should * be used to destroy transformation objects. * * The PROJ.4 library must be available at run-time. * * @param hSourceSRS source spatial reference system. * @param hTargetSRS target spatial reference system. * @return NULL on failure or a ready to use transformation object. */ OGRCoordinateTransformationH CPL_STDCALL OCTNewCoordinateTransformation( OGRSpatialReferenceH hSourceSRS, OGRSpatialReferenceH hTargetSRS ) { return reinterpret_cast<OGRCoordinateTransformationH>( OGRCreateCoordinateTransformation( reinterpret_cast<OGRSpatialReference *>(hSourceSRS), reinterpret_cast<OGRSpatialReference *>(hTargetSRS))); } /************************************************************************/ /* OGRProj4CT() */ /************************************************************************/ OGRProj4CT::OGRProj4CT() : poSRSSource(NULL), psPJSource(NULL), bSourceLatLong(false), dfSourceToRadians(0.0), bSourceWrap(false), dfSourceWrapLong(0.0), poSRSTarget(NULL), psPJTarget(NULL), bTargetLatLong(false), dfTargetFromRadians(0.0), bTargetWrap(false), dfTargetWrapLong(0.0), bIdentityTransform(false), bWebMercatorToWGS84(false), nErrorCount(0), bCheckWithInvertProj(false), dfThreshold(0.0), pjctx(NULL), nMaxCount(0), padfOriX(NULL), padfOriY(NULL), padfOriZ(NULL), padfTargetX(NULL), padfTargetY(NULL), padfTargetZ(NULL), m_bEmitErrors(true), bNoTransform(false) { if( pfn_pj_ctx_alloc != NULL ) pjctx = pfn_pj_ctx_alloc(); } /************************************************************************/ /* ~OGRProj4CT() */ /************************************************************************/ OGRProj4CT::~OGRProj4CT() { if( poSRSSource != NULL ) { if( poSRSSource->Dereference() <= 0 ) delete poSRSSource; } if( poSRSTarget != NULL ) { if( poSRSTarget->Dereference() <= 0 ) delete poSRSTarget; } if( pjctx != NULL ) { pfn_pj_ctx_free(pjctx); if( psPJSource != NULL ) pfn_pj_free( psPJSource ); if( psPJTarget != NULL ) pfn_pj_free( psPJTarget ); } else { CPLMutexHolderD( &hPROJMutex ); if( psPJSource != NULL ) pfn_pj_free( psPJSource ); if( psPJTarget != NULL ) pfn_pj_free( psPJTarget ); } CPLFree(padfOriX); CPLFree(padfOriY); CPLFree(padfOriZ); CPLFree(padfTargetX); CPLFree(padfTargetY); CPLFree(padfTargetZ); } /************************************************************************/ /* Initialize() */ /************************************************************************/ int OGRProj4CT::Initialize( OGRSpatialReference * poSourceIn, OGRSpatialReference * poTargetIn ) { if( bProjLocaleSafe ) { return InitializeNoLock(poSourceIn, poTargetIn); } CPLLocaleC oLocaleEnforcer; if( pjctx != NULL ) { return InitializeNoLock(poSourceIn, poTargetIn); } CPLMutexHolderD( &hPROJMutex ); return InitializeNoLock(poSourceIn, poTargetIn); } /************************************************************************/ /* InitializeNoLock() */ /************************************************************************/ int OGRProj4CT::InitializeNoLock( OGRSpatialReference * poSourceIn, OGRSpatialReference * poTargetIn ) { if( poSourceIn == NULL || poTargetIn == NULL ) return FALSE; poSRSSource = poSourceIn->Clone(); poSRSTarget = poTargetIn->Clone(); bSourceLatLong = CPL_TO_BOOL(poSRSSource->IsGeographic()); bTargetLatLong = CPL_TO_BOOL(poSRSTarget->IsGeographic()); /* -------------------------------------------------------------------- */ /* Setup source and target translations to radians for lat/long */ /* systems. */ /* -------------------------------------------------------------------- */ dfSourceToRadians = DEG_TO_RAD; bSourceWrap = false; dfSourceWrapLong = 0.0; if( bSourceLatLong ) { OGR_SRSNode *poUNITS = poSRSSource->GetAttrNode( "GEOGCS|UNIT" ); if( poUNITS && poUNITS->GetChildCount() >= 2 ) { dfSourceToRadians = CPLAtof(poUNITS->GetChild(1)->GetValue()); if( dfSourceToRadians == 0.0 ) dfSourceToRadians = DEG_TO_RAD; } } dfTargetFromRadians = RAD_TO_DEG; bTargetWrap = false; dfTargetWrapLong = 0.0; if( bTargetLatLong ) { OGR_SRSNode *poUNITS = poSRSTarget->GetAttrNode( "GEOGCS|UNIT" ); if( poUNITS && poUNITS->GetChildCount() >= 2 ) { const double dfTargetToRadians = CPLAtof(poUNITS->GetChild(1)->GetValue()); if( dfTargetToRadians != 0.0 ) dfTargetFromRadians = 1 / dfTargetToRadians; } } /* -------------------------------------------------------------------- */ /* Preliminary logic to setup wrapping. */ /* -------------------------------------------------------------------- */ if( CPLGetConfigOption( "CENTER_LONG", NULL ) != NULL ) { bSourceWrap = true; bTargetWrap = true; dfSourceWrapLong = dfTargetWrapLong = CPLAtof(CPLGetConfigOption( "CENTER_LONG", "" )); CPLDebug( "OGRCT", "Wrap at %g.", dfSourceWrapLong ); } const char *pszCENTER_LONG = poSRSSource->GetExtension( "GEOGCS", "CENTER_LONG" ); if( pszCENTER_LONG != NULL ) { dfSourceWrapLong = CPLAtof(pszCENTER_LONG); bSourceWrap = true; CPLDebug( "OGRCT", "Wrap source at %g.", dfSourceWrapLong ); } pszCENTER_LONG = poSRSTarget->GetExtension( "GEOGCS", "CENTER_LONG" ); if( pszCENTER_LONG != NULL ) { dfTargetWrapLong = CPLAtof(pszCENTER_LONG); bTargetWrap = true; CPLDebug( "OGRCT", "Wrap target at %g.", dfTargetWrapLong ); } bCheckWithInvertProj = CPLTestBool(CPLGetConfigOption( "CHECK_WITH_INVERT_PROJ", "NO" )); // The threshold is experimental. Works well with the cases of ticket #2305. if( bSourceLatLong ) dfThreshold = CPLAtof(CPLGetConfigOption( "THRESHOLD", ".1" )); else // 1 works well for most projections, except for +proj=aeqd that // requires a tolerance of 10000. dfThreshold = CPLAtof(CPLGetConfigOption( "THRESHOLD", "10000" )); // OGRThreadSafety: The following variable is not a thread safety issue // since the only issue is incrementing while accessing which at worse // means debug output could be one "increment" late. static int nDebugReportCount = 0; char *pszSrcProj4Defn = NULL; if( poSRSSource->exportToProj4( &pszSrcProj4Defn ) != OGRERR_NONE ) { CPLFree( pszSrcProj4Defn ); return FALSE; } if( strlen(pszSrcProj4Defn) == 0 ) { CPLFree( pszSrcProj4Defn ); CPLError( CE_Failure, CPLE_AppDefined, "No PROJ.4 translation for source SRS, coordinate " "transformation initialization has failed." ); return FALSE; } char *pszDstProj4Defn = NULL; if( poSRSTarget->exportToProj4( &pszDstProj4Defn ) != OGRERR_NONE ) { CPLFree( pszSrcProj4Defn ); CPLFree( pszDstProj4Defn ); return FALSE; } if( strlen(pszDstProj4Defn) == 0 ) { CPLFree( pszSrcProj4Defn ); CPLFree( pszDstProj4Defn ); CPLError( CE_Failure, CPLE_AppDefined, "No PROJ.4 translation for destination SRS, coordinate " "transformation initialization has failed." ); return FALSE; } /* -------------------------------------------------------------------- */ /* Optimization to avoid useless nadgrids evaluation. */ /* For example when converting between WGS84 and WebMercator */ /* -------------------------------------------------------------------- */ if( pszSrcProj4Defn[strlen(pszSrcProj4Defn)-1] == ' ' ) pszSrcProj4Defn[strlen(pszSrcProj4Defn)-1] = 0; if( pszDstProj4Defn[strlen(pszDstProj4Defn)-1] == ' ' ) pszDstProj4Defn[strlen(pszDstProj4Defn)-1] = 0; char* pszNeedle = strstr(pszSrcProj4Defn, " "); if( pszNeedle ) memmove(pszNeedle, pszNeedle + 1, strlen(pszNeedle + 1)+1); pszNeedle = strstr(pszDstProj4Defn, " "); if( pszNeedle ) memmove(pszNeedle, pszNeedle + 1, strlen(pszNeedle + 1)+1); if( (strstr(pszSrcProj4Defn, "+datum=WGS84") != NULL || strstr(pszSrcProj4Defn, "+ellps=WGS84 +towgs84=0,0,0,0,0,0,0 ") != NULL) && strstr(pszDstProj4Defn, "+nadgrids=@null ") != NULL && strstr(pszDstProj4Defn, "+towgs84") == NULL ) { char* pszDst = strstr(pszSrcProj4Defn, "+towgs84=0,0,0,0,0,0,0 "); if( pszDst != NULL ) { char *pszSrc = pszDst + strlen("+towgs84=0,0,0,0,0,0,0 "); memmove(pszDst, pszSrc, strlen(pszSrc)+1); } else { memcpy(strstr(pszSrcProj4Defn, "+datum=WGS84"), "+ellps", 6); } pszDst = strstr(pszDstProj4Defn, "+nadgrids=@null "); char *pszSrc = pszDst + strlen("+nadgrids=@null "); memmove(pszDst, pszSrc, strlen(pszSrc)+1); pszDst = strstr(pszDstProj4Defn, "+wktext "); if( pszDst ) { pszSrc = pszDst + strlen("+wktext "); memmove(pszDst, pszSrc, strlen(pszSrc)+1); } } else if( (strstr(pszDstProj4Defn, "+datum=WGS84") != NULL || strstr(pszDstProj4Defn, "+ellps=WGS84 +towgs84=0,0,0,0,0,0,0 ") != NULL) && strstr(pszSrcProj4Defn, "+nadgrids=@null ") != NULL && strstr(pszSrcProj4Defn, "+towgs84") == NULL ) { char* pszDst = strstr(pszDstProj4Defn, "+towgs84=0,0,0,0,0,0,0 "); if( pszDst != NULL) { char* pszSrc = pszDst + strlen("+towgs84=0,0,0,0,0,0,0 "); memmove(pszDst, pszSrc, strlen(pszSrc)+1); } else { memcpy(strstr(pszDstProj4Defn, "+datum=WGS84"), "+ellps", 6); } pszDst = strstr(pszSrcProj4Defn, "+nadgrids=@null "); char* pszSrc = pszDst + strlen("+nadgrids=@null "); memmove(pszDst, pszSrc, strlen(pszSrc)+1); pszDst = strstr(pszSrcProj4Defn, "+wktext "); if( pszDst ) { pszSrc = pszDst + strlen("+wktext "); memmove(pszDst, pszSrc, strlen(pszSrc)+1); } bWebMercatorToWGS84 = strcmp(pszDstProj4Defn, "+proj=longlat +ellps=WGS84 +no_defs") == 0 && strcmp(pszSrcProj4Defn, "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 " "+x_0=0.0 +y_0=0 +k=1.0 +units=m +no_defs") == 0; } /* -------------------------------------------------------------------- */ /* Establish PROJ.4 handle for source if projection. */ /* -------------------------------------------------------------------- */ if( !bWebMercatorToWGS84 ) { if( pjctx ) psPJSource = pfn_pj_init_plus_ctx( pjctx, pszSrcProj4Defn ); else psPJSource = pfn_pj_init_plus( pszSrcProj4Defn ); if( psPJSource == NULL ) { if( pjctx != NULL) { const int l_pj_errno = pfn_pj_ctx_get_errno(pjctx); // pfn_pj_strerrno not yet thread-safe in PROJ 4.8.0. CPLMutexHolderD(&hPROJMutex); CPLError( CE_Failure, CPLE_NotSupported, "Failed to initialize PROJ.4 with `%s'.\n%s", pszSrcProj4Defn, pfn_pj_strerrno(l_pj_errno) ); } else if( pfn_pj_get_errno_ref != NULL && pfn_pj_strerrno != NULL ) { const int *p_pj_errno = pfn_pj_get_errno_ref(); CPLError( CE_Failure, CPLE_NotSupported, "Failed to initialize PROJ.4 with `%s'.\n%s", pszSrcProj4Defn, pfn_pj_strerrno(*p_pj_errno) ); } else { CPLError( CE_Failure, CPLE_NotSupported, "Failed to initialize PROJ.4 with `%s'.", pszSrcProj4Defn ); } } } if( nDebugReportCount < 10 ) CPLDebug( "OGRCT", "Source: %s", pszSrcProj4Defn ); if( !bWebMercatorToWGS84 && psPJSource == NULL ) { CPLFree( pszSrcProj4Defn ); CPLFree( pszDstProj4Defn ); return FALSE; } /* -------------------------------------------------------------------- */ /* Establish PROJ.4 handle for target if projection. */ /* -------------------------------------------------------------------- */ if( !bWebMercatorToWGS84 ) { if( pjctx ) psPJTarget = pfn_pj_init_plus_ctx( pjctx, pszDstProj4Defn ); else psPJTarget = pfn_pj_init_plus( pszDstProj4Defn ); if( psPJTarget == NULL ) CPLError( CE_Failure, CPLE_NotSupported, "Failed to initialize PROJ.4 with `%s'.", pszDstProj4Defn ); } if( nDebugReportCount < 10 ) { CPLDebug( "OGRCT", "Target: %s", pszDstProj4Defn ); nDebugReportCount++; } if( !bWebMercatorToWGS84 && psPJTarget == NULL ) { CPLFree( pszSrcProj4Defn ); CPLFree( pszDstProj4Defn ); return FALSE; } // Determine if we really have a transformation to do at the proj.4 level // (but we may have a unit transformation to do) bIdentityTransform = strcmp(pszSrcProj4Defn, pszDstProj4Defn) == 0; // Determine if we can skip the transformation completely. // Assume that source and target units are defined with at least // 10 correct significant digits; hence the 1E-9 tolerance used. bNoTransform = bIdentityTransform && bSourceLatLong && !bSourceWrap && bTargetLatLong && !bTargetWrap && fabs(dfSourceToRadians * dfTargetFromRadians - 1.0) < 1E-9; CPLFree( pszSrcProj4Defn ); CPLFree( pszDstProj4Defn ); return TRUE; } /************************************************************************/ /* GetSourceCS() */ /************************************************************************/ OGRSpatialReference *OGRProj4CT::GetSourceCS() { return poSRSSource; } /************************************************************************/ /* GetTargetCS() */ /************************************************************************/ OGRSpatialReference *OGRProj4CT::GetTargetCS() { return poSRSTarget; } /************************************************************************/ /* Transform() */ /* */ /* This is a small wrapper for the extended transform version. */ /************************************************************************/ int OGRProj4CT::Transform( int nCount, double *x, double *y, double *z ) { int *pabSuccess = static_cast<int *>(CPLMalloc(sizeof(int) * nCount)); bool bOverallSuccess = CPL_TO_BOOL(TransformEx( nCount, x, y, z, pabSuccess )); for( int i = 0; i < nCount; i++ ) { if( !pabSuccess[i] ) { bOverallSuccess = false; break; } } CPLFree( pabSuccess ); return bOverallSuccess; } /************************************************************************/ /* OCTTransform() */ /************************************************************************/ /** Transform an array of points * * @param hTransform Transformation object * @param nCount Number of points * @param x Array of nCount x values. * @param y Array of nCount y values. * @param z Array of nCount z values. * @return TRUE or FALSE */ int CPL_STDCALL OCTTransform( OGRCoordinateTransformationH hTransform, int nCount, double *x, double *y, double *z ) { VALIDATE_POINTER1( hTransform, "OCTTransform", FALSE ); return ((OGRCoordinateTransformation*) hTransform)-> Transform( nCount, x, y, z ); } /************************************************************************/ /* TransformEx() */ /************************************************************************/ /** Transform an array of points * * @param nCount Number of points * @param x Array of nCount x values. * @param y Array of nCount y values. * @param z Array of nCount z values. * @param pabSuccess Output array of nCount value that will be set to TRUE/FALSE * @return TRUE or FALSE */ int OGRProj4CT::TransformEx( int nCount, double *x, double *y, double *z, int *pabSuccess ) { // Prevent any coordinate modification when possible if ( bNoTransform ) { if( pabSuccess ) { for( int i = 0; i < nCount; i++ ) { pabSuccess[i] = TRUE; } } return TRUE; } // Workaround potential bugs in proj.4 such as // the one of https://github.com/OSGeo/proj.4/commit/ // bc7453d1a75aab05bdff2c51ed78c908e3efa3cd for( int i = 0; i < nCount; i++ ) { if( CPLIsNan(x[i]) || CPLIsNan(y[i]) ) { x[i] = HUGE_VAL; y[i] = HUGE_VAL; } } /* -------------------------------------------------------------------- */ /* Potentially transform to radians. */ /* -------------------------------------------------------------------- */ if( bSourceLatLong ) { if( bSourceWrap ) { for( int i = 0; i < nCount; i++ ) { if( x[i] != HUGE_VAL && y[i] != HUGE_VAL ) { if( x[i] < dfSourceWrapLong - 180.0 ) x[i] += 360.0; else if( x[i] > dfSourceWrapLong + 180 ) x[i] -= 360.0; } } } for( int i = 0; i < nCount; i++ ) { if( x[i] != HUGE_VAL ) { x[i] *= dfSourceToRadians; y[i] *= dfSourceToRadians; } } } /* -------------------------------------------------------------------- */ /* Optimized transform from WebMercator to WGS84 */ /* -------------------------------------------------------------------- */ bool bTransformDone = false; if( bWebMercatorToWGS84 ) { static const double REVERSE_SPHERE_RADIUS = 1.0 / 6378137.0; double y0 = y[0]; for( int i = 0; i < nCount; i++ ) { if( x[i] != HUGE_VAL ) { x[i] = x[i] * REVERSE_SPHERE_RADIUS; if( x[i] > M_PI ) { if( x[i] < M_PI+1e-14 ) { x[i] = M_PI; } else if( bCheckWithInvertProj ) { x[i] = HUGE_VAL; y[i] = HUGE_VAL; y0 = HUGE_VAL; continue; } else { do { x[i] -= 2 * M_PI; } while( x[i] > M_PI ); } } else if( x[i] < -M_PI ) { if( x[i] > -M_PI-1e-14 ) { x[i] = -M_PI; } else if( bCheckWithInvertProj ) { x[i] = HUGE_VAL; y[i] = HUGE_VAL; y0 = HUGE_VAL; continue; } else { do { x[i] += 2 * M_PI; } while( x[i] < -M_PI ); } } // Optimization for the case where we are provided a whole line // of same northing. if( i > 0 && y[i] == y0 ) y[i] = y[0]; else y[i] = M_PI / 2.0 - 2.0 * atan(exp(-y[i] * REVERSE_SPHERE_RADIUS)); } } bTransformDone = true; } else if( bIdentityTransform ) { bTransformDone = true; } /* -------------------------------------------------------------------- */ /* Do the transformation (or not...) using PROJ.4. */ /* -------------------------------------------------------------------- */ if( !bTransformDone && pjctx == NULL ) { // The mutex has already been created. CPLAssert(hPROJMutex != NULL); CPLAcquireMutex(hPROJMutex, 1000.0); } int err = 0; if( bTransformDone ) { // err = 0; } else if( bCheckWithInvertProj ) { // For some projections, we cannot detect if we are trying to reproject // coordinates outside the validity area of the projection. So let's do // the reverse reprojection and compare with the source coordinates. if( nCount > nMaxCount ) { nMaxCount = nCount; padfOriX = static_cast<double*>( CPLRealloc(padfOriX, sizeof(double) * nCount)); padfOriY = static_cast<double*>( CPLRealloc(padfOriY, sizeof(double)*nCount)); padfOriZ = static_cast<double*>( CPLRealloc(padfOriZ, sizeof(double)*nCount)); padfTargetX = static_cast<double*>( CPLRealloc(padfTargetX, sizeof(double)*nCount)); padfTargetY = static_cast<double*>( CPLRealloc(padfTargetY, sizeof(double)*nCount)); padfTargetZ = static_cast<double*>( CPLRealloc(padfTargetZ, sizeof(double)*nCount)); } memcpy(padfOriX, x, sizeof(double) * nCount); memcpy(padfOriY, y, sizeof(double) * nCount); if( z ) { memcpy(padfOriZ, z, sizeof(double)*nCount); } err = pfn_pj_transform( psPJSource, psPJTarget, nCount, 1, x, y, z ); if( err == 0 ) { memcpy(padfTargetX, x, sizeof(double) * nCount); memcpy(padfTargetY, y, sizeof(double) * nCount); if( z ) { memcpy(padfTargetZ, z, sizeof(double) * nCount); } err = pfn_pj_transform( psPJTarget, psPJSource , nCount, 1, padfTargetX, padfTargetY, z ? padfTargetZ : NULL); if( err == 0 ) { for( int i = 0; i < nCount; i++ ) { if( x[i] != HUGE_VAL && y[i] != HUGE_VAL && (fabs(padfTargetX[i] - padfOriX[i]) > dfThreshold || fabs(padfTargetY[i] - padfOriY[i]) > dfThreshold) ) { x[i] = HUGE_VAL; y[i] = HUGE_VAL; } } } } } else { err = pfn_pj_transform( psPJSource, psPJTarget, nCount, 1, x, y, z ); } /* -------------------------------------------------------------------- */ /* Try to report an error through CPL. Get proj.4 error string */ /* if possible. Try to avoid reporting thousands of errors. */ /* Suppress further error reporting on this OGRProj4CT if we */ /* have already reported 20 errors. */ /* -------------------------------------------------------------------- */ if( err != 0 ) { if( pabSuccess ) memset( pabSuccess, 0, sizeof(int) * nCount ); if( m_bEmitErrors && ++nErrorCount < 20 ) { if( pjctx != NULL ) // pfn_pj_strerrno not yet thread-safe in PROJ 4.8.0. CPLAcquireMutex(hPROJMutex, 1000.0); const char *pszError = NULL; if( pfn_pj_strerrno != NULL ) pszError = pfn_pj_strerrno( err ); if( pszError == NULL ) CPLError( CE_Failure, CPLE_AppDefined, "Reprojection failed, err = %d", err ); else CPLError( CE_Failure, CPLE_AppDefined, "%s", pszError ); if( pjctx != NULL ) // pfn_pj_strerrno not yet thread-safe in PROJ 4.8.0. CPLReleaseMutex(hPROJMutex); } else if( nErrorCount == 20 ) { CPLError( CE_Failure, CPLE_AppDefined, "Reprojection failed, err = %d, further errors will be " "suppressed on the transform object.", err ); } if( pjctx == NULL ) CPLReleaseMutex(hPROJMutex); return FALSE; } if( !bTransformDone && pjctx == NULL ) CPLReleaseMutex(hPROJMutex); /* -------------------------------------------------------------------- */ /* Potentially transform back to degrees. */ /* -------------------------------------------------------------------- */ if( bTargetLatLong ) { for( int i = 0; i < nCount; i++ ) { if( x[i] != HUGE_VAL && y[i] != HUGE_VAL ) { x[i] *= dfTargetFromRadians; y[i] *= dfTargetFromRadians; } } if( bTargetWrap ) { for( int i = 0; i < nCount; i++ ) { if( x[i] != HUGE_VAL && y[i] != HUGE_VAL ) { if( x[i] < dfTargetWrapLong - 180.0 ) x[i] += 360.0; else if( x[i] > dfTargetWrapLong + 180 ) x[i] -= 360.0; } } } } /* -------------------------------------------------------------------- */ /* Establish error information if pabSuccess provided. */ /* -------------------------------------------------------------------- */ if( pabSuccess ) { for( int i = 0; i < nCount; i++ ) { if( x[i] == HUGE_VAL || y[i] == HUGE_VAL ) pabSuccess[i] = FALSE; else pabSuccess[i] = TRUE; } } return TRUE; } /************************************************************************/ /* OCTTransformEx() */ /************************************************************************/ /** Transform an array of points * * @param hTransform Transformation object * @param nCount Number of points * @param x Array of nCount x values. * @param y Array of nCount y values. * @param z Array of nCount z values. * @param pabSuccess Output array of nCount value that will be set to TRUE/FALSE * @return TRUE or FALSE */ int CPL_STDCALL OCTTransformEx( OGRCoordinateTransformationH hTransform, int nCount, double *x, double *y, double *z, int *pabSuccess ) { VALIDATE_POINTER1( hTransform, "OCTTransformEx", FALSE ); return ((OGRCoordinateTransformation*) hTransform)-> TransformEx( nCount, x, y, z, pabSuccess ); }