EVOLUTION-MANAGER
Edit File: ogr_fromepsg.cpp
/****************************************************************************** * * Project: OpenGIS Simple Features Reference Implementation * Purpose: Generate an OGRSpatialReference object based on an EPSG * PROJCS, or GEOGCS code. * 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_srs_api.h" #include <ctype.h> #include <cmath> #include <cstddef> #include <cstdio> #include <cstring> #include <cstdlib> #include <map> #include <memory> #include <string> #include <vector> #include <limits> #include "cpl_conv.h" #include "cpl_csv.h" #include "cpl_error.h" #include "cpl_multiproc.h" #include "cpl_string.h" #include "ogr_core.h" #include "ogr_p.h" #include "ogr_spatialref.h" CPL_CVSID("$Id: ogr_fromepsg.cpp a5d470bdd8e89e3146e962ff5909fa30e2cb6cdf 2018-04-18 22:09:11 +0200 Even Rouault $") extern void OGRsnPrintDouble( char * pszStrBuf, size_t size, double dfValue ); int EPSGGetWGS84Transform( int nGeogCS, std::vector<CPLString>& asTransform ); void OGREPSGDatumNameMassage( char ** ppszDatum ); void CleanupFindMatchesCacheAndMutex(); static const char * const apszDatumEquiv[] = { "Militar_Geographische_Institut", "Militar_Geographische_Institute", "World_Geodetic_System_1984", "WGS_1984", "WGS_72_Transit_Broadcast_Ephemeris", "WGS_1972_Transit_Broadcast_Ephemeris", "World_Geodetic_System_1972", "WGS_1972", "European_Terrestrial_Reference_System_89", "European_Reference_System_1989", nullptr }; static CPLMutex* hFindMatchesMutex = nullptr; static std::vector<OGRSpatialReference*>* papoSRSCache_PROJCS = nullptr; static std::vector<OGRSpatialReference*>* papoSRSCache_GEOGCS = nullptr; static std::map<CPLString, int>* poMapESRIPROJCSNameToEPSGCode = nullptr; static std::map<CPLString, int>* poMapESRIGEOGCSNameToEPSGCode = nullptr; /************************************************************************/ /* OGREPSGDatumNameMassage() */ /* */ /* Massage an EPSG datum name into WMT format. Also transform */ /* specific exception cases into WKT versions. */ /************************************************************************/ void OGREPSGDatumNameMassage( char ** ppszDatum ) { char *pszDatum = *ppszDatum; if( pszDatum[0] == '\0' ) return; /* -------------------------------------------------------------------- */ /* Translate non-alphanumeric values to underscores. */ /* -------------------------------------------------------------------- */ for( int i = 0; pszDatum[i] != '\0'; i++ ) { if( pszDatum[i] != '+' && !(pszDatum[i] >= 'A' && pszDatum[i] <= 'Z') && !(pszDatum[i] >= 'a' && pszDatum[i] <= 'z') && !(pszDatum[i] >= '0' && pszDatum[i] <= '9') ) { pszDatum[i] = '_'; } } /* -------------------------------------------------------------------- */ /* Remove repeated and trailing underscores. */ /* -------------------------------------------------------------------- */ int j = 0; // Used after for loop. for( int i = 1; pszDatum[i] != '\0'; i++ ) { if( pszDatum[j] == '_' && pszDatum[i] == '_' ) continue; pszDatum[++j] = pszDatum[i]; } if( pszDatum[j] == '_' ) pszDatum[j] = '\0'; else pszDatum[j+1] = '\0'; /* -------------------------------------------------------------------- */ /* Search for datum equivelences. Specific massaged names get */ /* mapped to OpenGIS specified names. */ /* -------------------------------------------------------------------- */ for( int i = 0; apszDatumEquiv[i] != nullptr; i += 2 ) { if( EQUAL(*ppszDatum, apszDatumEquiv[i]) ) { CPLFree( *ppszDatum ); *ppszDatum = CPLStrdup( apszDatumEquiv[i+1] ); break; } } } /************************************************************************/ /* EPSGAngleStringToDD() */ /* */ /* Convert an angle in the specified units to decimal degrees. */ /************************************************************************/ static double EPSGAngleStringToDD( const char * pszAngle, int nUOMAngle ) { double dfAngle = 0.0; if( nUOMAngle == 9110 ) // DDD.MMSSsss { dfAngle = std::abs(atoi(pszAngle)); const char *pszDecimal = strchr(pszAngle, '.'); if( pszDecimal != nullptr && strlen(pszDecimal) > 1 ) { char szMinutes[3] = { '\0', '\0', '\0' }; szMinutes[0] = pszDecimal[1]; if( pszDecimal[2] >= '0' && pszDecimal[2] <= '9' ) szMinutes[1] = pszDecimal[2]; else szMinutes[1] = '0'; dfAngle += atoi(szMinutes) / 60.0; if( strlen(pszDecimal) > 3 ) { char szSeconds[64] = { '\0' }; szSeconds[0] = pszDecimal[3]; if( pszDecimal[4] >= '0' && pszDecimal[4] <= '9' ) { szSeconds[1] = pszDecimal[4]; szSeconds[2] = '.'; strncpy( szSeconds+3, pszDecimal + 5, sizeof(szSeconds)-3-1 ); szSeconds[sizeof(szSeconds)-1] = 0; } else { szSeconds[1] = '0'; szSeconds[2] = '\0'; } dfAngle += CPLAtof(szSeconds) / 3600.0; } } if( pszAngle[0] == '-' ) dfAngle *= -1; } else if( nUOMAngle == 9105 || nUOMAngle == 9106 ) // Grad. { dfAngle = 180.0 * (CPLAtof(pszAngle ) / 200.0); } else if( nUOMAngle == 9101 ) // Radians. { dfAngle = 180.0 * (CPLAtof(pszAngle ) / M_PI); } else if( nUOMAngle == 9103 ) // Arc-minute. { dfAngle = CPLAtof(pszAngle) / 60.0; } else if( nUOMAngle == 9104 ) // Arc-second. { dfAngle = CPLAtof(pszAngle) / 3600.0; } else // Decimal degrees. Some cases missing, but seemingly never used. { CPLAssert( nUOMAngle == 9102 || nUOMAngle == 0 ); dfAngle = CPLAtof(pszAngle ); } return dfAngle; } /************************************************************************/ /* EPSGGetUOMAngleInfo() */ /************************************************************************/ static bool EPSGGetUOMAngleInfo( int nUOMAngleCode, char **ppszUOMName, double * pdfInDegrees ) { // We do a special override of some of the DMS formats name // This will also solve accuracy problems when computing // the dfInDegree value from the CSV values (#3643). if( nUOMAngleCode == 9102 || nUOMAngleCode == 9107 || nUOMAngleCode == 9108 || nUOMAngleCode == 9110 || nUOMAngleCode == 9122 ) { if( ppszUOMName != nullptr ) *ppszUOMName = CPLStrdup("degree"); if( pdfInDegrees != nullptr ) *pdfInDegrees = 1.0; return true; } const char *pszFilename = CSVFilename( "unit_of_measure.csv" ); char szSearchKey[24] = { '\0' }; snprintf( szSearchKey, sizeof(szSearchKey), "%d", nUOMAngleCode ); const char *pszUOMName = CSVGetField( pszFilename, "UOM_CODE", szSearchKey, CC_Integer, "UNIT_OF_MEAS_NAME" ); /* -------------------------------------------------------------------- */ /* If the file is found, read from there. Note that FactorC is */ /* an empty field for any of the DMS style formats, and in this */ /* case we really want to return the default InDegrees value */ /* (1.0) from above. */ /* -------------------------------------------------------------------- */ double dfInDegrees = 1.0; if( !EQUAL( pszUOMName, "" ) ) { const double dfFactorB = CPLAtof(CSVGetField( pszFilename, "UOM_CODE", szSearchKey, CC_Integer, "FACTOR_B" )); const double dfFactorC = CPLAtof(CSVGetField( pszFilename, "UOM_CODE", szSearchKey, CC_Integer, "FACTOR_C" )); if( dfFactorC != 0.0 ) dfInDegrees = (dfFactorB / dfFactorC) * (180.0 / M_PI); // For some reason, (FactorB) is not very precise in EPSG, use // a more exact form for grads. if( nUOMAngleCode == 9105 ) dfInDegrees = 180.0 / 200.0; } /* -------------------------------------------------------------------- */ /* Otherwise handle a few well known units directly. */ /* -------------------------------------------------------------------- */ else { switch( nUOMAngleCode ) { case 9101: pszUOMName = "radian"; dfInDegrees = 180.0 / M_PI; break; case 9103: pszUOMName = "arc-minute"; dfInDegrees = 1 / 60.0; break; case 9104: pszUOMName = "arc-second"; dfInDegrees = 1 / 3600.0; break; case 9105: pszUOMName = "grad"; dfInDegrees = 180.0 / 200.0; break; case 9106: pszUOMName = "gon"; dfInDegrees = 180.0 / 200.0; break; case 9109: pszUOMName = "microradian"; dfInDegrees = 180.0 / (M_PI * 1000000.0); break; default: return false; } } /* -------------------------------------------------------------------- */ /* Return to caller. */ /* -------------------------------------------------------------------- */ if( ppszUOMName != nullptr ) *ppszUOMName = CPLStrdup( pszUOMName ); if( pdfInDegrees != nullptr ) *pdfInDegrees = dfInDegrees; return true; } /************************************************************************/ /* EPSGGetUOMLengthInfo() */ /* */ /* Note: This function should eventually also know how to */ /* lookup length aliases in the UOM_LE_ALIAS table. */ /************************************************************************/ static bool EPSGGetUOMLengthInfo( int nUOMLengthCode, char **ppszUOMName, double * pdfInMeters ) { /* -------------------------------------------------------------------- */ /* We short cut meter to save work in the most common case. */ /* -------------------------------------------------------------------- */ if( nUOMLengthCode == 9001 ) { if( ppszUOMName != nullptr ) *ppszUOMName = CPLStrdup( "metre" ); if( pdfInMeters != nullptr ) *pdfInMeters = 1.0; return true; } /* -------------------------------------------------------------------- */ /* Search the units database for this unit. If we don't find */ /* it return failure. */ /* -------------------------------------------------------------------- */ const char *uom_filename = CSVFilename( "unit_of_measure.csv" ); char szSearchKey[24] = { '\0' }; snprintf( szSearchKey, sizeof(szSearchKey), "%d", nUOMLengthCode ); char **papszUnitsRecord = CSVScanFileByName( uom_filename, "UOM_CODE", szSearchKey, CC_Integer ); if( papszUnitsRecord == nullptr ) return false; /* -------------------------------------------------------------------- */ /* Get the name, if requested. */ /* -------------------------------------------------------------------- */ if( ppszUOMName != nullptr ) { const int iNameField = CSVGetFileFieldId( uom_filename, "UNIT_OF_MEAS_NAME" ); *ppszUOMName = CPLStrdup( CSLGetField(papszUnitsRecord, iNameField) ); } /* -------------------------------------------------------------------- */ /* Get the A and B factor fields, and create the multiplicative */ /* factor. */ /* -------------------------------------------------------------------- */ if( pdfInMeters != nullptr ) { const int iBFactorField = CSVGetFileFieldId( uom_filename, "FACTOR_B" ); const int iCFactorField = CSVGetFileFieldId( uom_filename, "FACTOR_C" ); if( CPLAtof(CSLGetField(papszUnitsRecord, iCFactorField)) > 0.0 ) *pdfInMeters = CPLAtof(CSLGetField(papszUnitsRecord, iBFactorField)) / CPLAtof(CSLGetField(papszUnitsRecord, iCFactorField)); else *pdfInMeters = 0.0; } return true; } /************************************************************************/ /* EPSGNegateString() */ /************************************************************************/ static void EPSGNegateString(CPLString& osValue) { if( osValue.compare("0") == 0 ) return; if( osValue[0] == '-' ) { osValue = osValue.substr(1); return; } if( osValue[0] == '+' ) { osValue[0] = '-'; return; } osValue = "-" + osValue; } /************************************************************************/ /* EPSGGetWGS84Transform() */ /* */ /* The following code attempts to find a bursa-wolf */ /* transformation from this GeogCS to WGS84 (4326). */ /* */ /* Faults: */ /* o I think there are codes other than 9603 and 9607 that */ /* return compatible, or easily transformed parameters. */ /* o Only the first path from the given GeogCS is checked due */ /* to limitations in the CSV API. */ /************************************************************************/ int EPSGGetWGS84Transform( int nGeogCS, std::vector<CPLString>& asTransform ) { /* -------------------------------------------------------------------- */ /* Fetch the line from the GCS table. */ /* -------------------------------------------------------------------- */ const char *pszFilename = CSVFilename("gcs.override.csv"); char szCode[32] = { '\0' }; snprintf( szCode, sizeof(szCode), "%d", nGeogCS ); char **papszLine = CSVScanFileByName( pszFilename, "COORD_REF_SYS_CODE", szCode, CC_Integer ); if( papszLine == nullptr ) { pszFilename = CSVFilename("gcs.csv"); snprintf( szCode, sizeof(szCode), "%d", nGeogCS ); papszLine = CSVScanFileByName( pszFilename, "COORD_REF_SYS_CODE", szCode, CC_Integer ); } if( papszLine == nullptr ) return FALSE; /* -------------------------------------------------------------------- */ /* Verify that the method code is one of our accepted ones. */ /* -------------------------------------------------------------------- */ const int nMethodCode = atoi(CSLGetField( papszLine, CSVGetFileFieldId(pszFilename, "COORD_OP_METHOD_CODE"))); if( nMethodCode != 9603 && nMethodCode != 9607 && nMethodCode != 9606 ) return FALSE; /* -------------------------------------------------------------------- */ /* Fetch the transformation parameters. */ /* -------------------------------------------------------------------- */ const int iDXField = CSVGetFileFieldId(pszFilename, "DX"); if( iDXField < 0 || CSLCount(papszLine) < iDXField + 7 ) return FALSE; asTransform.resize(0); for( int iField = 0; iField < 7; iField++ ) { const char* pszValue = papszLine[iDXField+iField]; if( pszValue[0] ) asTransform.push_back(pszValue); else asTransform.push_back("0"); } /* -------------------------------------------------------------------- */ /* 9607 - coordinate frame rotation has reverse signs on the */ /* rotational coefficients. Fix up now since we internal */ /* operate according to method 9606 (position vector 7-parameter). */ /* -------------------------------------------------------------------- */ if( nMethodCode == 9607 ) { EPSGNegateString(asTransform[3]); EPSGNegateString(asTransform[4]); EPSGNegateString(asTransform[5]); } return TRUE; } /************************************************************************/ /* EPSGGetPMInfo() */ /* */ /* Get the offset between a given prime meridian and Greenwich */ /* in degrees. */ /************************************************************************/ static bool EPSGGetPMInfo( int nPMCode, char ** ppszName, double *pdfOffset ) { /* -------------------------------------------------------------------- */ /* Use a special short cut for Greenwich, since it is so common. */ /* -------------------------------------------------------------------- */ // FIXME? Where does 7022 come from ? Let's keep it just in case // 8901 is the official current code for Greenwich. if( nPMCode == 7022 /* PM_Greenwich */ || nPMCode == 8901 ) { if( pdfOffset != nullptr ) *pdfOffset = 0.0; if( ppszName != nullptr ) *ppszName = CPLStrdup( "Greenwich" ); return true; } /* -------------------------------------------------------------------- */ /* Search the database for the corresponding datum code. */ /* -------------------------------------------------------------------- */ char szSearchKey[24] = { '\0' }; snprintf( szSearchKey, sizeof(szSearchKey), "%d", nPMCode ); const char *PM_FILENAME = CSVFilename("prime_meridian.csv"); const int nUOMAngle = atoi(CSVGetField( PM_FILENAME, "PRIME_MERIDIAN_CODE", szSearchKey, CC_Integer, "UOM_CODE" ) ); if( nUOMAngle < 1 ) return false; /* -------------------------------------------------------------------- */ /* Get the PM offset. */ /* -------------------------------------------------------------------- */ if( pdfOffset != nullptr ) { *pdfOffset = EPSGAngleStringToDD( CSVGetField( PM_FILENAME, "PRIME_MERIDIAN_CODE", szSearchKey, CC_Integer, "GREENWICH_LONGITUDE" ), nUOMAngle ); } /* -------------------------------------------------------------------- */ /* Get the name, if requested. */ /* -------------------------------------------------------------------- */ if( ppszName != nullptr ) *ppszName = CPLStrdup( CSVGetField( PM_FILENAME, "PRIME_MERIDIAN_CODE", szSearchKey, CC_Integer, "PRIME_MERIDIAN_NAME" )); return true; } /************************************************************************/ /* EPSGGetGCSInfo() */ /* */ /* Fetch the datum, and prime meridian related to a particular */ /* GCS. */ /************************************************************************/ static bool EPSGGetGCSInfo( int nGCSCode, char ** ppszName, int * pnDatum, char **ppszDatumName, int * pnPM, int *pnEllipsoid, int *pnUOMAngle, int * pnCoordSysCode ) { /* -------------------------------------------------------------------- */ /* Search the database for the corresponding datum code. */ /* -------------------------------------------------------------------- */ const char *pszFilename = CSVFilename("gcs.override.csv"); char szSearchKey[24] = { '\0' }; snprintf( szSearchKey, sizeof(szSearchKey), "%d", nGCSCode ); int nDatum = atoi(CSVGetField( pszFilename, "COORD_REF_SYS_CODE", szSearchKey, CC_Integer, "DATUM_CODE" ) ); if( nDatum < 1 ) { pszFilename = CSVFilename("gcs.csv"); snprintf( szSearchKey, sizeof(szSearchKey), "%d", nGCSCode ); nDatum = atoi(CSVGetField( pszFilename, "COORD_REF_SYS_CODE", szSearchKey, CC_Integer, "DATUM_CODE" ) ); } if( nDatum < 1 ) return false; if( pnDatum != nullptr ) *pnDatum = nDatum; /* -------------------------------------------------------------------- */ /* Get the PM. */ /* -------------------------------------------------------------------- */ const int nPM = atoi(CSVGetField( pszFilename, "COORD_REF_SYS_CODE", szSearchKey, CC_Integer, "PRIME_MERIDIAN_CODE" ) ); if( nPM < 1 ) return false; if( pnPM != nullptr ) *pnPM = nPM; /* -------------------------------------------------------------------- */ /* Get the Ellipsoid. */ /* -------------------------------------------------------------------- */ const int nEllipsoid = atoi(CSVGetField( pszFilename, "COORD_REF_SYS_CODE", szSearchKey, CC_Integer, "ELLIPSOID_CODE" ) ); if( nEllipsoid < 1 ) return false; if( pnEllipsoid != nullptr ) *pnEllipsoid = nEllipsoid; /* -------------------------------------------------------------------- */ /* Get the angular units. */ /* -------------------------------------------------------------------- */ const int nUOMAngle = atoi(CSVGetField( pszFilename, "COORD_REF_SYS_CODE", szSearchKey, CC_Integer, "UOM_CODE" ) ); if( nUOMAngle < 1 ) return false; if( pnUOMAngle != nullptr ) *pnUOMAngle = nUOMAngle; /* -------------------------------------------------------------------- */ /* Get the name, if requested. */ /* -------------------------------------------------------------------- */ if( ppszName != nullptr ) *ppszName = CPLStrdup(CSVGetField( pszFilename, "COORD_REF_SYS_CODE", szSearchKey, CC_Integer, "COORD_REF_SYS_NAME" )); /* -------------------------------------------------------------------- */ /* Get the datum name, if requested. */ /* -------------------------------------------------------------------- */ if( ppszDatumName != nullptr ) *ppszDatumName = CPLStrdup(CSVGetField( pszFilename, "COORD_REF_SYS_CODE", szSearchKey, CC_Integer, "DATUM_NAME" )); /* -------------------------------------------------------------------- */ /* Get the CoordSysCode */ /* -------------------------------------------------------------------- */ const int nCSC = atoi(CSVGetField( pszFilename, "COORD_REF_SYS_CODE", szSearchKey, CC_Integer, "COORD_SYS_CODE" ) ); if( pnCoordSysCode != nullptr ) *pnCoordSysCode = nCSC; return TRUE; } /************************************************************************/ /* OSRGetEllipsoidInfo() */ /************************************************************************/ /** * Fetch info about an ellipsoid. * * This helper function will return ellipsoid parameters corresponding to EPSG * code provided. Axes are always returned in meters. Semi major computed * based on inverse flattening where that is provided. * * @param nCode EPSG code of the requested ellipsoid * * @param ppszName pointer to string where ellipsoid name will be returned. It * is caller responsibility to free this string after using with CPLFree(). * * @param pdfSemiMajor pointer to variable where semi major axis will be * returned. * * @param pdfInvFlattening pointer to variable where inverse flattening will * be returned. * * @return OGRERR_NONE on success or an error code in case of failure. **/ OGRErr OSRGetEllipsoidInfo( int nCode, char ** ppszName, double * pdfSemiMajor, double * pdfInvFlattening ) { /* -------------------------------------------------------------------- */ /* Get the semi major axis. */ /* -------------------------------------------------------------------- */ char szSearchKey[24] = { '\0' }; snprintf( szSearchKey, sizeof(szSearchKey), "%d", nCode ); szSearchKey[sizeof(szSearchKey) - 1] = '\n'; double dfSemiMajor = CPLAtof(CSVGetField( CSVFilename("ellipsoid.csv" ), "ELLIPSOID_CODE", szSearchKey, CC_Integer, "SEMI_MAJOR_AXIS" ) ); if( dfSemiMajor == 0.0 ) return OGRERR_UNSUPPORTED_SRS; /* -------------------------------------------------------------------- */ /* Get the translation factor into meters. */ /* -------------------------------------------------------------------- */ const int nUOMLength = atoi(CSVGetField( CSVFilename("ellipsoid.csv" ), "ELLIPSOID_CODE", szSearchKey, CC_Integer, "UOM_CODE" )); double dfToMeters = 1.0; if( !EPSGGetUOMLengthInfo( nUOMLength, nullptr, &dfToMeters ) ) { dfToMeters = 1.0; } dfSemiMajor *= dfToMeters; if( pdfSemiMajor != nullptr ) *pdfSemiMajor = dfSemiMajor; /* -------------------------------------------------------------------- */ /* Get the semi-minor if requested. If the Semi-minor axis */ /* isn't available, compute it based on the inverse flattening. */ /* -------------------------------------------------------------------- */ if( pdfInvFlattening != nullptr ) { *pdfInvFlattening = CPLAtof(CSVGetField( CSVFilename("ellipsoid.csv" ), "ELLIPSOID_CODE", szSearchKey, CC_Integer, "INV_FLATTENING" )); if( *pdfInvFlattening == 0.0 ) { const double dfSemiMinor = CPLAtof(CSVGetField( CSVFilename("ellipsoid.csv" ), "ELLIPSOID_CODE", szSearchKey, CC_Integer, "SEMI_MINOR_AXIS" )) * dfToMeters; if( dfSemiMajor == 0.0 ) *pdfInvFlattening = 0.0; else *pdfInvFlattening = OSRCalcInvFlattening(dfSemiMajor, dfSemiMinor); } } /* -------------------------------------------------------------------- */ /* Get the name, if requested. */ /* -------------------------------------------------------------------- */ if( ppszName != nullptr ) *ppszName = CPLStrdup(CSVGetField( CSVFilename("ellipsoid.csv" ), "ELLIPSOID_CODE", szSearchKey, CC_Integer, "ELLIPSOID_NAME" )); return OGRERR_NONE; } constexpr int CoLatConeAxis = 1036; // See #4223. constexpr int NatOriginLat = 8801; constexpr int NatOriginLong = 8802; constexpr int NatOriginScaleFactor = 8805; constexpr int FalseEasting = 8806; constexpr int FalseNorthing = 8807; constexpr int ProjCenterLat = 8811; constexpr int ProjCenterLong = 8812; constexpr int Azimuth = 8813; constexpr int AngleRectifiedToSkewedGrid = 8814; constexpr int InitialLineScaleFactor = 8815; constexpr int ProjCenterEasting = 8816; constexpr int ProjCenterNorthing = 8817; constexpr int PseudoStdParallelLat = 8818; constexpr int PseudoStdParallelScaleFactor = 8819; constexpr int FalseOriginLat = 8821; constexpr int FalseOriginLong = 8822; constexpr int StdParallel1Lat = 8823; constexpr int StdParallel2Lat = 8824; constexpr int FalseOriginEasting = 8826; constexpr int FalseOriginNorthing = 8827; constexpr int SphericalOriginLat = 8828; constexpr int SphericalOriginLong = 8829; #if 0 constexpr int InitialLongitude = 8830; constexpr int ZoneWidth = 8831; #endif constexpr int PolarLatStdParallel = 8832; constexpr int PolarLongOrigin = 8833; /************************************************************************/ /* EPSGGetProjTRFInfo() */ /* */ /* Transform a PROJECTION_TRF_CODE into a projection method, */ /* and a set of parameters. The parameters identify will */ /* depend on the returned method, but they will all have been */ /* normalized into degrees and meters. */ /************************************************************************/ static bool EPSGGetProjTRFInfo( int nPCS, int * pnProjMethod, int *panParmIds, double * padfProjParms ) { /* -------------------------------------------------------------------- */ /* Get the proj method. If this fails to return a meaningful */ /* number, then the whole function fails. */ /* -------------------------------------------------------------------- */ CPLString osFilename = CSVFilename( "pcs.override.csv" ); char szTRFCode[16] = { '\0' }; snprintf( szTRFCode, sizeof(szTRFCode), "%d", nPCS ); int nProjMethod = atoi( CSVGetField( osFilename, "COORD_REF_SYS_CODE", szTRFCode, CC_Integer, "COORD_OP_METHOD_CODE" ) ); if( nProjMethod == 0 ) { osFilename = CSVFilename( "pcs.csv" ); snprintf( szTRFCode, sizeof(szTRFCode), "%d", nPCS ); nProjMethod = atoi( CSVGetField( osFilename, "COORD_REF_SYS_CODE", szTRFCode, CC_Integer, "COORD_OP_METHOD_CODE" ) ); if( nProjMethod == 0 ) return false; } /* -------------------------------------------------------------------- */ /* Get the parameters for this projection. */ /* -------------------------------------------------------------------- */ double adfProjParms[7] = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; for( int i = 0; i < 7; i++ ) { if( panParmIds == nullptr ) { CPLError( CE_Failure, CPLE_AppDefined, "panParmIds cannot be NULL." ); return false; } char szParamUOMID[32] = { '\0' }; char szParamValueID[32] = { '\0' }; char szParamCodeID[32] = { '\0' }; snprintf( szParamCodeID, sizeof(szParamCodeID), "PARAMETER_CODE_%d", i+1 ); snprintf( szParamUOMID, sizeof(szParamUOMID), "PARAMETER_UOM_%d", i+1 ); snprintf( szParamValueID, sizeof(szParamValueID), "PARAMETER_VALUE_%d", i+1 ); panParmIds[i] = atoi(CSVGetField( osFilename, "COORD_REF_SYS_CODE", szTRFCode, CC_Integer, szParamCodeID )); int nUOM = atoi(CSVGetField( osFilename, "COORD_REF_SYS_CODE", szTRFCode, CC_Integer, szParamUOMID )); char *pszValue = CPLStrdup( CSVGetField( osFilename, "COORD_REF_SYS_CODE", szTRFCode, CC_Integer, szParamValueID )); // there is a bug in the EPSG 6.2.2 database for PCS 2935 and 2936 // such that they have foot units for the scale factor. Avoid this. if( (panParmIds[i] == NatOriginScaleFactor || panParmIds[i] == InitialLineScaleFactor || panParmIds[i] == PseudoStdParallelScaleFactor) && nUOM < 9200 ) nUOM = 9201; if( nUOM >= 9100 && nUOM < 9200 ) { adfProjParms[i] = EPSGAngleStringToDD( pszValue, nUOM ); } else if( nUOM > 9000 && nUOM < 9100 ) { double dfInMeters = 0.0; if( !EPSGGetUOMLengthInfo( nUOM, nullptr, &dfInMeters ) ) dfInMeters = 1.0; adfProjParms[i] = CPLAtof(pszValue) * dfInMeters; } else if( EQUAL(pszValue, "") ) // Null field. { adfProjParms[i] = 0.0; } else // Really, should consider looking up other scaling factors. { if( nUOM != 9201 ) CPLDebug( "OGR", "Non-unity scale factor units! (UOM=%d, PCS=%d)", nUOM, nPCS ); adfProjParms[i] = CPLAtof(pszValue); } CPLFree( pszValue ); } /* -------------------------------------------------------------------- */ /* Transfer requested data into passed variables. */ /* -------------------------------------------------------------------- */ if( pnProjMethod != nullptr ) *pnProjMethod = nProjMethod; if( padfProjParms != nullptr ) { for( int i = 0; i < 7; i++ ) padfProjParms[i] = adfProjParms[i]; } return true; } /************************************************************************/ /* EPSGGetPCSInfo() */ /************************************************************************/ static bool EPSGGetPCSInfo( int nPCSCode, char **ppszEPSGName, int *pnUOMLengthCode, int *pnUOMAngleCode, int *pnGeogCS, int *pnTRFCode, int *pnCoordSysCode, double* adfTOWGS84 ) { /* -------------------------------------------------------------------- */ /* Search the units database for this unit. If we don't find */ /* it return failure. */ /* -------------------------------------------------------------------- */ const char *pszFilename = CSVFilename( "pcs.override.csv" ); char szSearchKey[24] = { '\0' }; snprintf( szSearchKey, sizeof(szSearchKey), "%d", nPCSCode ); char **papszRecord = CSVScanFileByName( pszFilename, "COORD_REF_SYS_CODE", szSearchKey, CC_Integer ); if( papszRecord == nullptr ) { pszFilename = CSVFilename( "pcs.csv" ); snprintf( szSearchKey, sizeof(szSearchKey), "%d", nPCSCode ); papszRecord = CSVScanFileByName( pszFilename, "COORD_REF_SYS_CODE", szSearchKey, CC_Integer ); } if( papszRecord == nullptr ) return false; /* -------------------------------------------------------------------- */ /* Get the name, if requested. */ /* -------------------------------------------------------------------- */ if( ppszEPSGName != nullptr ) { CPLString osPCSName = CSLGetField( papszRecord, CSVGetFileFieldId(pszFilename, "COORD_REF_SYS_NAME")); const char *pszDeprecated = CSLGetField( papszRecord, CSVGetFileFieldId(pszFilename, "DEPRECATED") ); if( pszDeprecated != nullptr && *pszDeprecated == '1' ) osPCSName += " (deprecated)"; *ppszEPSGName = CPLStrdup(osPCSName); } /* -------------------------------------------------------------------- */ /* Get the UOM Length code, if requested. */ /* -------------------------------------------------------------------- */ if( pnUOMLengthCode != nullptr ) { const char *pszValue = CSLGetField( papszRecord, CSVGetFileFieldId(pszFilename, "UOM_CODE")); if( atoi(pszValue) > 0 ) *pnUOMLengthCode = atoi(pszValue); else *pnUOMLengthCode = 0; } /* -------------------------------------------------------------------- */ /* Get the UOM Angle code, if requested. */ /* -------------------------------------------------------------------- */ if( pnUOMAngleCode != nullptr ) { const char *pszValue = CSLGetField( papszRecord, CSVGetFileFieldId(pszFilename, "UOM_ANGLE_CODE") ); if( atoi(pszValue) > 0 ) *pnUOMAngleCode = atoi(pszValue); else *pnUOMAngleCode = 0; } /* -------------------------------------------------------------------- */ /* Get the GeogCS (Datum with PM) code, if requested. */ /* -------------------------------------------------------------------- */ if( pnGeogCS != nullptr ) { const char *pszValue = CSLGetField( papszRecord, CSVGetFileFieldId(pszFilename, "SOURCE_GEOGCRS_CODE")); if( atoi(pszValue) > 0 ) *pnGeogCS = atoi(pszValue); else *pnGeogCS = 0; } /* -------------------------------------------------------------------- */ /* Get the GeogCS (Datum with PM) code, if requested. */ /* -------------------------------------------------------------------- */ if( pnTRFCode != nullptr ) { const char *pszValue = CSLGetField( papszRecord, CSVGetFileFieldId(pszFilename, "COORD_OP_CODE")); if( atoi(pszValue) > 0 ) *pnTRFCode = atoi(pszValue); else *pnTRFCode = 0; } /* -------------------------------------------------------------------- */ /* Get the CoordSysCode */ /* -------------------------------------------------------------------- */ const int nCSC = atoi(CSVGetField( pszFilename, "COORD_REF_SYS_CODE", szSearchKey, CC_Integer, "COORD_SYS_CODE" ) ); if( pnCoordSysCode != nullptr ) *pnCoordSysCode = nCSC; /* -------------------------------------------------------------------- */ /* Get the TOWGS84 (override) parameters */ /* -------------------------------------------------------------------- */ const char *pszDX = CSLGetField( papszRecord, CSVGetFileFieldId(pszFilename, "DX")); if( pszDX[0] != '\0' ) { adfTOWGS84[0] = CPLAtof(pszDX); adfTOWGS84[1] = CPLAtof(CSLGetField( papszRecord, CSVGetFileFieldId(pszFilename, "DY"))); adfTOWGS84[2] = CPLAtof(CSLGetField( papszRecord, CSVGetFileFieldId(pszFilename, "DZ"))); adfTOWGS84[3] = CPLAtof(CSLGetField( papszRecord, CSVGetFileFieldId(pszFilename, "RX"))); adfTOWGS84[4] = CPLAtof(CSLGetField( papszRecord, CSVGetFileFieldId(pszFilename, "RY"))); adfTOWGS84[5] = CPLAtof(CSLGetField( papszRecord, CSVGetFileFieldId(pszFilename, "RZ"))); adfTOWGS84[6] = CPLAtof(CSLGetField( papszRecord, CSVGetFileFieldId(pszFilename, "DS"))); } return true; } /************************************************************************/ /* SetEPSGAxisInfo() */ /************************************************************************/ static OGRErr SetEPSGAxisInfo( OGRSpatialReference *poSRS, const char *pszTargetKey, int nCoordSysCode ) { /* -------------------------------------------------------------------- */ /* Special cases for well known and common values. We short */ /* circuit these to save time doing file lookups. */ /* -------------------------------------------------------------------- */ // Conventional and common Easting/Northing values. if( nCoordSysCode >= 4400 && nCoordSysCode <= 4410 ) { return poSRS->SetAxes( pszTargetKey, "Easting", OAO_East, "Northing", OAO_North ); } // Conventional and common Easting/Northing values. if( nCoordSysCode >= 6400 && nCoordSysCode <= 6423 ) { return poSRS->SetAxes( pszTargetKey, "Latitude", OAO_North, "Longitude", OAO_East ); } /* -------------------------------------------------------------------- */ /* Get the definition from the coordinate_axis.csv file. */ /* -------------------------------------------------------------------- */ char szSearchKey[24] = { '\0' }; const char *pszFilename = CSVFilename( "coordinate_axis.csv" ); snprintf( szSearchKey, sizeof(szSearchKey), "%d", nCoordSysCode ); char **papszRecord = CSVScanFileByName( pszFilename, "COORD_SYS_CODE", szSearchKey, CC_Integer ); char **papszAxis1 = nullptr; char **papszAxis2 = nullptr; if( papszRecord != nullptr ) { papszAxis1 = CSLDuplicate( papszRecord ); papszRecord = CSVGetNextLine( pszFilename ); if( CSLCount(papszRecord) > 0 && EQUAL(papszRecord[0], papszAxis1[0]) ) papszAxis2 = CSLDuplicate( papszRecord ); } if( papszAxis2 == nullptr ) { CSLDestroy( papszAxis1 ); CPLError( CE_Failure, CPLE_AppDefined, "Failed to find entries for COORD_SYS_CODE %d " "in coordinate_axis.csv", nCoordSysCode ); return OGRERR_FAILURE; } /* -------------------------------------------------------------------- */ /* Confirm the records are complete, and work out which columns */ /* are which. */ /* -------------------------------------------------------------------- */ const int iAxisOrientationField = CSVGetFileFieldId( pszFilename, "coord_axis_orientation" ); const int iAxisAbbrevField = CSVGetFileFieldId( pszFilename, "coord_axis_abbreviation" ); const int iAxisOrderField = CSVGetFileFieldId( pszFilename, "coord_axis_order" ); const int iAxisNameCodeField = CSVGetFileFieldId( pszFilename, "coord_axis_name_code" ); // Check that all fields are available and that the axis_order field // is the one with highest index. if( !(iAxisOrientationField >= 0 && iAxisOrientationField < iAxisOrderField && iAxisAbbrevField >= 0 && iAxisAbbrevField < iAxisOrderField && iAxisOrderField >= 0 && iAxisNameCodeField >= 0 && iAxisNameCodeField < iAxisOrderField) ) { CSLDestroy( papszAxis1 ); CSLDestroy( papszAxis2 ); CPLError( CE_Failure, CPLE_AppDefined, "coordinate_axis.csv corrupted" ); return OGRERR_FAILURE; } if( CSLCount(papszAxis1) < iAxisOrderField+1 || CSLCount(papszAxis2) < iAxisOrderField+1 ) { CSLDestroy( papszAxis1 ); CSLDestroy( papszAxis2 ); CPLError( CE_Failure, CPLE_AppDefined, "Axis records appear incomplete for COORD_SYS_CODE %d " "in coordinate_axis.csv", nCoordSysCode ); return OGRERR_FAILURE; } /* -------------------------------------------------------------------- */ /* Do we need to switch the axes around? */ /* -------------------------------------------------------------------- */ if( atoi(papszAxis2[iAxisOrderField]) < atoi(papszAxis1[iAxisOrderField]) ) { papszRecord = papszAxis1; papszAxis1 = papszAxis2; papszAxis2 = papszRecord; } /* -------------------------------------------------------------------- */ /* Work out axis enumeration values. */ /* -------------------------------------------------------------------- */ OGRAxisOrientation eOAxis1 = OAO_Other; OGRAxisOrientation eOAxis2 = OAO_Other; constexpr int anCodes[7] = { -1, 9907, 9909, 9906, 9908, -1, -1 }; for( int iAO = 0; iAO < 7; iAO++ ) { const OGRAxisOrientation eAO = static_cast<OGRAxisOrientation>(iAO); if( EQUAL(papszAxis1[iAxisOrientationField], OSRAxisEnumToName(eAO)) ) eOAxis1 = eAO; if( EQUAL(papszAxis2[iAxisOrientationField], OSRAxisEnumToName(eAO)) ) eOAxis2 = eAO; if( eOAxis1 == OAO_Other && anCodes[iAO] == atoi(papszAxis1[iAxisNameCodeField]) ) eOAxis1 = eAO; if( eOAxis2 == OAO_Other && anCodes[iAO] == atoi(papszAxis2[iAxisNameCodeField]) ) eOAxis2 = eAO; } /* -------------------------------------------------------------------- */ /* Work out the axis name. We try to expand the abbreviation */ /* to a longer name. */ /* -------------------------------------------------------------------- */ const char *apszAxisName[2] = { papszAxis1[iAxisAbbrevField], papszAxis2[iAxisAbbrevField] }; for( int iAO = 0; iAO < 2; iAO++ ) { if( EQUAL(apszAxisName[iAO], "N") ) apszAxisName[iAO] = "Northing"; else if( EQUAL(apszAxisName[iAO], "E") ) apszAxisName[iAO] = "Easting"; else if( EQUAL(apszAxisName[iAO], "S") ) apszAxisName[iAO] = "Southing"; else if( EQUAL(apszAxisName[iAO], "W") ) apszAxisName[iAO] = "Westing"; } /* -------------------------------------------------------------------- */ /* Set the axes. */ /* -------------------------------------------------------------------- */ const OGRErr eResult = poSRS->SetAxes( pszTargetKey, apszAxisName[0], eOAxis1, apszAxisName[1], eOAxis2 ); CSLDestroy( papszAxis1 ); CSLDestroy( papszAxis2 ); return eResult; } /************************************************************************/ /* SetEPSGGeogCS() */ /* */ /* FLAWS: */ /* o Units are all hardcoded. */ /************************************************************************/ static OGRErr SetEPSGGeogCS( OGRSpatialReference * poSRS, int nGeogCS ) { int nDatumCode = 0; int nPMCode = 0; int nUOMAngle = 0; int nEllipsoidCode = 0; int nCSC = 0; char *pszGeogCSName = nullptr; char *pszDatumName = nullptr; char *pszAngleName = nullptr; if( !EPSGGetGCSInfo( nGeogCS, &pszGeogCSName, &nDatumCode, &pszDatumName, &nPMCode, &nEllipsoidCode, &nUOMAngle, &nCSC ) ) return OGRERR_UNSUPPORTED_SRS; char *pszPMName = nullptr; double dfPMOffset = 0.0; if( !EPSGGetPMInfo( nPMCode, &pszPMName, &dfPMOffset ) ) { CPLFree( pszDatumName ); CPLFree( pszGeogCSName ); return OGRERR_UNSUPPORTED_SRS; } OGREPSGDatumNameMassage( &pszDatumName ); char *pszEllipsoidName = nullptr; double dfSemiMajor = 0.0; double dfInvFlattening = 0.0; if( OSRGetEllipsoidInfo( nEllipsoidCode, &pszEllipsoidName, &dfSemiMajor, &dfInvFlattening ) != OGRERR_NONE ) { CPLFree( pszDatumName ); CPLFree( pszGeogCSName ); CPLFree( pszPMName ); return OGRERR_UNSUPPORTED_SRS; } double dfAngleInDegrees = 0.0; double dfAngleInRadians = 0.0; if( !EPSGGetUOMAngleInfo( nUOMAngle, &pszAngleName, &dfAngleInDegrees ) ) { pszAngleName = CPLStrdup("degree"); dfAngleInDegrees = 1.0; nUOMAngle = -1; } if( dfAngleInDegrees == 1.0 ) dfAngleInRadians = CPLAtof(SRS_UA_DEGREE_CONV); else dfAngleInRadians = CPLAtof(SRS_UA_DEGREE_CONV) * dfAngleInDegrees; poSRS->SetGeogCS( pszGeogCSName, pszDatumName, pszEllipsoidName, dfSemiMajor, dfInvFlattening, pszPMName, dfPMOffset, pszAngleName, dfAngleInRadians ); std::vector<CPLString> asBursaTransform; if( EPSGGetWGS84Transform( nGeogCS, asBursaTransform ) ) { OGR_SRSNode *poWGS84 = new OGR_SRSNode( "TOWGS84" ); for( int iCoeff = 0; iCoeff < 7; iCoeff++ ) { poWGS84->AddChild( new OGR_SRSNode( asBursaTransform[iCoeff].c_str() ) ); } poSRS->GetAttrNode( "DATUM" )->AddChild( poWGS84 ); } poSRS->SetAuthority( "GEOGCS", "EPSG", nGeogCS ); poSRS->SetAuthority( "DATUM", "EPSG", nDatumCode ); poSRS->SetAuthority( "SPHEROID", "EPSG", nEllipsoidCode ); poSRS->SetAuthority( "PRIMEM", "EPSG", nPMCode ); if( nUOMAngle > 0 ) poSRS->SetAuthority( "GEOGCS|UNIT", "EPSG", nUOMAngle ); CPLFree( pszAngleName ); CPLFree( pszDatumName ); CPLFree( pszEllipsoidName ); CPLFree( pszGeogCSName ); CPLFree( pszPMName ); /* -------------------------------------------------------------------- */ /* Set axes */ /* -------------------------------------------------------------------- */ if( nCSC > 0 ) { SetEPSGAxisInfo( poSRS, "GEOGCS", nCSC ); CPLErrorReset(); } return OGRERR_NONE; } /************************************************************************/ /* OGR_FetchParm() */ /* */ /* Fetch a parameter from the parm list, based on its EPSG */ /* parameter code. */ /************************************************************************/ static double OGR_FetchParm( double *padfProjParms, int *panParmIds, int nTargetId, double /* dfFromGreenwich */) { /* -------------------------------------------------------------------- */ /* Set default in meters/degrees. */ /* -------------------------------------------------------------------- */ double dfResult = 0.0; switch( nTargetId ) { case NatOriginScaleFactor: case InitialLineScaleFactor: case PseudoStdParallelScaleFactor: dfResult = 1.0; break; case AngleRectifiedToSkewedGrid: dfResult = 90.0; break; default: dfResult = 0.0; } /* -------------------------------------------------------------------- */ /* Try to find actual value in parameter list. */ /* -------------------------------------------------------------------- */ for( int i = 0; i < 7; i++ ) { if( panParmIds[i] == nTargetId ) { dfResult = padfProjParms[i]; break; } } /* -------------------------------------------------------------------- */ /* EPSG longitudes are relative to greenwich. The follow code */ /* could be used to make them relative to the prime meridian of */ /* the associated GCS if that was appropriate. However, the */ /* SetNormProjParm() method expects longitudes relative to */ /* greenwich, so there is nothing for us to do. */ /* -------------------------------------------------------------------- */ #if 0 switch( nTargetId ) { case NatOriginLong: case ProjCenterLong: case FalseOriginLong: case SphericalOriginLong: case InitialLongitude: // Note that the EPSG values are already relative to greenwich. // This shift is really making it relative to the provided prime // meridian, so that when SetTM() and company the correction back // ends up back relative to greenwich. dfResult = dfResult + dfFromGreenwich; break; default: ; } #endif return dfResult; } #define OGR_FP(x) OGR_FetchParm(adfProjParms, anParmIds, (x), dfFromGreenwich) /************************************************************************/ /* SetEPSGProjCS() */ /************************************************************************/ static OGRErr SetEPSGProjCS( OGRSpatialReference * poSRS, int nPCSCode ) { int nGCSCode = 0; int nUOMAngleCode = 0; int nUOMLength = 0; int nTRFCode = 0; int nCSC = 0; char *pszPCSName = nullptr; double adfTOWGS84[7] = { std::numeric_limits<double>::infinity(), std::numeric_limits<double>::infinity(), std::numeric_limits<double>::infinity(), std::numeric_limits<double>::infinity(), std::numeric_limits<double>::infinity(), std::numeric_limits<double>::infinity(), std::numeric_limits<double>::infinity() }; if( !EPSGGetPCSInfo( nPCSCode, &pszPCSName, &nUOMLength, &nUOMAngleCode, &nGCSCode, &nTRFCode, &nCSC, adfTOWGS84 ) ) { CPLFree(pszPCSName); return OGRERR_UNSUPPORTED_SRS; } poSRS->SetNode( "PROJCS", pszPCSName ); /* -------------------------------------------------------------------- */ /* Set GEOGCS. */ /* -------------------------------------------------------------------- */ const OGRErr nErr = SetEPSGGeogCS( poSRS, nGCSCode ); if( nErr != OGRERR_NONE ) { CPLFree(pszPCSName); return nErr; } /* -------------------------------------------------------------------- */ /* Set overridden TOWGS84 parameters */ /* -------------------------------------------------------------------- */ if( adfTOWGS84[0] != std::numeric_limits<double>::infinity() ) { poSRS->SetTOWGS84( adfTOWGS84[0], adfTOWGS84[1], adfTOWGS84[2], adfTOWGS84[3], adfTOWGS84[4], adfTOWGS84[5], adfTOWGS84[6] ); } // Used by OGR_FP macro const double dfFromGreenwich = poSRS->GetPrimeMeridian(); /* -------------------------------------------------------------------- */ /* Set linear units. */ /* -------------------------------------------------------------------- */ char *pszUOMLengthName = nullptr; double dfInMeters = 0.0; if( !EPSGGetUOMLengthInfo( nUOMLength, &pszUOMLengthName, &dfInMeters ) ) { CPLFree(pszPCSName); return OGRERR_UNSUPPORTED_SRS; } poSRS->SetLinearUnits( pszUOMLengthName, dfInMeters ); poSRS->SetAuthority( "PROJCS|UNIT", "EPSG", nUOMLength ); CPLFree( pszUOMLengthName ); CPLFree( pszPCSName ); /* -------------------------------------------------------------------- */ /* Set projection and parameters. */ /* -------------------------------------------------------------------- */ int nProjMethod = 0; int anParmIds[7] = {}; double adfProjParms[7] = {}; if( !EPSGGetProjTRFInfo( nPCSCode, &nProjMethod, anParmIds, adfProjParms )) return OGRERR_UNSUPPORTED_SRS; switch( nProjMethod ) { case 9801: case 9817: // Really LCC near conformal. poSRS->SetLCC1SP( OGR_FP( NatOriginLat ), OGR_FP( NatOriginLong ), OGR_FP( NatOriginScaleFactor ), OGR_FP( FalseEasting ), OGR_FP( FalseNorthing ) ); break; case 9802: poSRS->SetLCC( OGR_FP( StdParallel1Lat ), OGR_FP( StdParallel2Lat ), OGR_FP( FalseOriginLat ), OGR_FP( FalseOriginLong ), OGR_FP( FalseOriginEasting ), OGR_FP( FalseOriginNorthing )); break; case 9803: poSRS->SetLCCB( OGR_FP( StdParallel1Lat ), OGR_FP( StdParallel2Lat ), OGR_FP( FalseOriginLat ), OGR_FP( FalseOriginLong ), OGR_FP( FalseOriginEasting ), OGR_FP( FalseOriginNorthing )); break; case 9805: poSRS->SetMercator2SP( OGR_FP( StdParallel1Lat ), OGR_FP( NatOriginLat ), OGR_FP(NatOriginLong), OGR_FP( FalseEasting ), OGR_FP(FalseNorthing) ); break; case 9804: case 9841: // Mercator 1SP (Spherical). case 1024: // Google Mercator. poSRS->SetMercator( OGR_FP( NatOriginLat ), OGR_FP( NatOriginLong ), OGR_FP( NatOriginScaleFactor ), OGR_FP( FalseEasting ), OGR_FP( FalseNorthing ) ); // override hack for google mercator. if( nProjMethod == 1024 || nProjMethod == 9841 ) { poSRS->SetExtension( "PROJCS", "PROJ4", "+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 +nadgrids=@null " "+wktext +no_defs" ); } break; case 9806: poSRS->SetCS( OGR_FP( NatOriginLat ), OGR_FP( NatOriginLong ), OGR_FP( FalseEasting ), OGR_FP( FalseNorthing ) ); break; case 9807: poSRS->SetTM( OGR_FP( NatOriginLat ), OGR_FP( NatOriginLong ), OGR_FP( NatOriginScaleFactor ), OGR_FP( FalseEasting ), OGR_FP( FalseNorthing ) ); break; case 9808: poSRS->SetTMSO( OGR_FP( NatOriginLat ), OGR_FP( NatOriginLong ), OGR_FP( NatOriginScaleFactor ), OGR_FP( FalseEasting ), OGR_FP( FalseNorthing ) ); break; case 9809: poSRS->SetOS( OGR_FP( NatOriginLat ), OGR_FP( NatOriginLong ), OGR_FP( NatOriginScaleFactor ), OGR_FP( FalseEasting ), OGR_FP( FalseNorthing ) ); break; case 9810: poSRS->SetPS( OGR_FP( NatOriginLat ), OGR_FP( NatOriginLong ), OGR_FP( NatOriginScaleFactor ), OGR_FP( FalseEasting ), OGR_FP( FalseNorthing ) ); break; case 9811: poSRS->SetNZMG( OGR_FP( NatOriginLat ), OGR_FP( NatOriginLong ), OGR_FP( FalseEasting ), OGR_FP( FalseNorthing ) ); break; case 9812: case 9813: { poSRS->SetHOM( OGR_FP( ProjCenterLat ), OGR_FP( ProjCenterLong ), OGR_FP( Azimuth ), OGR_FP( AngleRectifiedToSkewedGrid ), OGR_FP( InitialLineScaleFactor ), OGR_FP( FalseEasting ), OGR_FP( FalseNorthing ) ); OGR_SRSNode *poNode = poSRS->GetAttrNode( "PROJECTION" )->GetChild( 0 ); if( nProjMethod == 9813 ) poNode->SetValue( SRS_PT_LABORDE_OBLIQUE_MERCATOR ); break; } case 9814: // NOTE: This is no longer used. Swiss Oblique Mercator gets // implemented using 9815 instead. poSRS->SetSOC( OGR_FP( ProjCenterLat ), OGR_FP( ProjCenterLong ), OGR_FP( FalseEasting ), OGR_FP( FalseNorthing ) ); break; case 9815: poSRS->SetHOMAC( OGR_FP( ProjCenterLat ), OGR_FP( ProjCenterLong ), OGR_FP( Azimuth ), OGR_FP( AngleRectifiedToSkewedGrid ), OGR_FP( InitialLineScaleFactor ), OGR_FP( ProjCenterEasting ), OGR_FP( ProjCenterNorthing ) ); break; case 9816: poSRS->SetTMG( OGR_FP( FalseOriginLat ), OGR_FP( FalseOriginLong ), OGR_FP( FalseOriginEasting ), OGR_FP( FalseOriginNorthing ) ); break; case 9818: poSRS->SetPolyconic( OGR_FP( NatOriginLat ), OGR_FP( NatOriginLong ), OGR_FP( FalseEasting ), OGR_FP( FalseNorthing ) ); break; case 1041: // Used by EPSG:5514. case 9819: { double dfCenterLong = OGR_FP( ProjCenterLong ); if( dfCenterLong == 0.0 ) // See ticket #2559. dfCenterLong = OGR_FP( PolarLongOrigin ); double dfAzimuth = OGR_FP( CoLatConeAxis ); // See ticket #4223. if( dfAzimuth == 0.0 ) dfAzimuth = OGR_FP( Azimuth ); poSRS->SetKrovak( OGR_FP( ProjCenterLat ), dfCenterLong, dfAzimuth, OGR_FP( PseudoStdParallelLat ), OGR_FP( PseudoStdParallelScaleFactor ), OGR_FP( ProjCenterEasting ), OGR_FP( ProjCenterNorthing ) ); } break; case 9820: case 1027: // Used by EPSG:2163, 3408, 3409, 3973 and 3974. poSRS->SetLAEA( OGR_FP( NatOriginLat ), OGR_FP( NatOriginLong ), OGR_FP( FalseEasting ), OGR_FP( FalseNorthing ) ); break; case 9821: // DEPRECATED : this is the spherical form, and really needs // different equations which give different results but PROJ.4 // doesn't seem to support the spherical form. poSRS->SetLAEA( OGR_FP( SphericalOriginLat ), OGR_FP( SphericalOriginLong ), OGR_FP( FalseEasting ), OGR_FP( FalseNorthing ) ); break; case 9822: // Albers (Conic) Equal Area. poSRS->SetACEA( OGR_FP( StdParallel1Lat ), OGR_FP( StdParallel2Lat ), OGR_FP( FalseOriginLat ), OGR_FP( FalseOriginLong ), OGR_FP( FalseOriginEasting ), OGR_FP( FalseOriginNorthing ) ); break; case 9823: // Equidistant Cylindrical / Plate Carre / Equirectangular. case 9842: case 1028: case 1029: poSRS->SetEquirectangular( OGR_FP( NatOriginLat ), OGR_FP( NatOriginLong ), 0.0, 0.0 ); break; case 9829: // Polar Stereographic (Variant B). poSRS->SetPS( OGR_FP( PolarLatStdParallel ), OGR_FP(PolarLongOrigin), 1.0, OGR_FP( FalseEasting ), OGR_FP( FalseNorthing ) ); break; case 9834: // Lambert Cylindrical Equal Area (Spherical) bug #2659. case 9835: // Lambert Cylindrical Equal Area (Ellipsoidal). poSRS->SetCEA( OGR_FP( StdParallel1Lat ), OGR_FP( NatOriginLong ), OGR_FP( FalseEasting ), OGR_FP( FalseNorthing ) ); break; default: CPLDebug( "EPSG", "No WKT support for projection method %d.", nProjMethod ); return OGRERR_UNSUPPORTED_SRS; } /* -------------------------------------------------------------------- */ /* Set overall PCS authority code. */ /* -------------------------------------------------------------------- */ poSRS->SetAuthority( "PROJCS", "EPSG", nPCSCode ); /* -------------------------------------------------------------------- */ /* Set axes */ /* -------------------------------------------------------------------- */ if( nCSC > 0 ) { SetEPSGAxisInfo( poSRS, "PROJCS", nCSC ); CPLErrorReset(); } return OGRERR_NONE; } /************************************************************************/ /* SetEPSGVertCS() */ /************************************************************************/ static OGRErr SetEPSGVertCS( OGRSpatialReference * poSRS, int nVertCSCode ) { /* -------------------------------------------------------------------- */ /* Fetch record from the vertcs.csv or override file. */ /* -------------------------------------------------------------------- */ const char *pszFilename = CSVFilename( "vertcs.override.csv" ); char szSearchKey[24] = {}; snprintf( szSearchKey, sizeof(szSearchKey), "%d", nVertCSCode ); char **papszRecord = CSVScanFileByName( pszFilename, "COORD_REF_SYS_CODE", szSearchKey, CC_Integer ); if( papszRecord == nullptr ) { pszFilename = CSVFilename( "vertcs.csv" ); papszRecord = CSVScanFileByName( pszFilename, "COORD_REF_SYS_CODE", szSearchKey, CC_Integer ); } if( papszRecord == nullptr ) return OGRERR_UNSUPPORTED_SRS; /* -------------------------------------------------------------------- */ /* Setup the basic VERT_CS. */ /* -------------------------------------------------------------------- */ poSRS->SetVertCS( CSLGetField( papszRecord, CSVGetFileFieldId(pszFilename, "COORD_REF_SYS_NAME")), CSLGetField( papszRecord, CSVGetFileFieldId(pszFilename, "DATUM_NAME")) ); /* -------------------------------------------------------------------- */ /* Should we add a geoidgrids extension node? */ /* -------------------------------------------------------------------- */ const char *pszMethod = CSLGetField( papszRecord, CSVGetFileFieldId(pszFilename, "COORD_OP_METHOD_CODE_1")); if( pszMethod && EQUAL(pszMethod, "9665") ) { const char *pszParm11 = CSLGetField( papszRecord, CSVGetFileFieldId(pszFilename, "PARM_1_1")); poSRS->SetExtension( "VERT_CS|VERT_DATUM", "PROJ4_GRIDS", pszParm11 ); } /* -------------------------------------------------------------------- */ /* Setup the VERT_DATUM node. */ /* -------------------------------------------------------------------- */ poSRS->SetAuthority( "VERT_CS|VERT_DATUM", "EPSG", atoi(CSLGetField( papszRecord, CSVGetFileFieldId(pszFilename, "DATUM_CODE"))) ); /* -------------------------------------------------------------------- */ /* Set linear units. */ /* -------------------------------------------------------------------- */ const int nUOM_CODE = atoi(CSLGetField( papszRecord, CSVGetFileFieldId(pszFilename, "UOM_CODE"))); char *pszUOMLengthName = nullptr; double dfInMeters = 0.0; if( !EPSGGetUOMLengthInfo( nUOM_CODE, &pszUOMLengthName, &dfInMeters ) ) { CPLError( CE_Failure, CPLE_AppDefined, "Failed to lookup UOM CODE %d", nUOM_CODE ); } else { poSRS->SetTargetLinearUnits( "VERT_CS", pszUOMLengthName, dfInMeters ); poSRS->SetAuthority( "VERT_CS|UNIT", "EPSG", nUOM_CODE ); CPLFree( pszUOMLengthName ); } /* -------------------------------------------------------------------- */ /* Set overall authority code. */ /* -------------------------------------------------------------------- */ poSRS->SetAuthority( "VERT_CS", "EPSG", nVertCSCode ); return OGRERR_NONE; } /************************************************************************/ /* SetEPSGCompdCS() */ /************************************************************************/ static OGRErr SetEPSGCompdCS( OGRSpatialReference * poSRS, int nCCSCode ) { /* -------------------------------------------------------------------- */ /* Fetch record from the compdcs.csv or override file. */ /* -------------------------------------------------------------------- */ char szSearchKey[24] = {}; snprintf( szSearchKey, sizeof(szSearchKey), "%d", nCCSCode ); // So far no override file needed. // pszFilename = CSVFilename( "compdcs.override.csv" ); // papszRecord = CSVScanFileByName( pszFilename, "COORD_REF_SYS_CODE", // szSearchKey, CC_Integer ); //if( papszRecord == NULL ) const char *pszFilename = CSVFilename( "compdcs.csv" ); char **papszRecord = CSVScanFileByName( pszFilename, "COORD_REF_SYS_CODE", szSearchKey, CC_Integer ); if( papszRecord == nullptr ) return OGRERR_UNSUPPORTED_SRS; /* -------------------------------------------------------------------- */ /* Fetch subinformation now before anything messes with the */ /* last loaded record. */ /* -------------------------------------------------------------------- */ const int nPCSCode = atoi(CSLGetField( papszRecord, CSVGetFileFieldId(pszFilename, "CMPD_HORIZCRS_CODE"))); const int nVertCSCode = atoi(CSLGetField( papszRecord, CSVGetFileFieldId(pszFilename, "CMPD_VERTCRS_CODE"))); /* -------------------------------------------------------------------- */ /* Set the COMPD_CS node with a name. */ /* -------------------------------------------------------------------- */ poSRS->SetNode( "COMPD_CS", CSLGetField( papszRecord, CSVGetFileFieldId(pszFilename, "COORD_REF_SYS_NAME")) ); /* -------------------------------------------------------------------- */ /* Lookup the projected coordinate system. Can the */ /* horizontal CRS be a GCS? */ /* -------------------------------------------------------------------- */ OGRSpatialReference oPCS; OGRErr eErr = SetEPSGProjCS( &oPCS, nPCSCode ); if( eErr != OGRERR_NONE ) { // perhaps it is a GCS? eErr = SetEPSGGeogCS( &oPCS, nPCSCode ); } if( eErr != OGRERR_NONE ) { return eErr; } poSRS->GetRoot()->AddChild( oPCS.GetRoot()->Clone() ); /* -------------------------------------------------------------------- */ /* Lookup the VertCS. */ /* -------------------------------------------------------------------- */ OGRSpatialReference oVertCS; eErr = SetEPSGVertCS( &oVertCS, nVertCSCode ); if( eErr != OGRERR_NONE ) return eErr; poSRS->GetRoot()->AddChild( oVertCS.GetRoot()->Clone() ); /* -------------------------------------------------------------------- */ /* Set overall authority code. */ /* -------------------------------------------------------------------- */ poSRS->SetAuthority( "COMPD_CS", "EPSG", nCCSCode ); return OGRERR_NONE; } /************************************************************************/ /* SetEPSGGeocCS() */ /************************************************************************/ static OGRErr SetEPSGGeocCS( OGRSpatialReference * poSRS, int nGCSCode ) { /* -------------------------------------------------------------------- */ /* Fetch record from the geoccs.csv or override file. */ /* -------------------------------------------------------------------- */ char szSearchKey[24] = {}; snprintf( szSearchKey, sizeof(szSearchKey), "%d", nGCSCode ); // So far no override file needed. // pszFilename = CSVFilename( "compdcs.override.csv" ); // papszRecord = CSVScanFileByName( pszFilename, "COORD_REF_SYS_CODE", // szSearchKey, CC_Integer ); // if( papszRecord == NULL ) const char *pszFilename = CSVFilename( "geoccs.csv" ); char **papszRecord = CSVScanFileByName( pszFilename, "COORD_REF_SYS_CODE", szSearchKey, CC_Integer ); if( papszRecord == nullptr ) return OGRERR_UNSUPPORTED_SRS; /* -------------------------------------------------------------------- */ /* Set the GEOCCS node with a name. */ /* -------------------------------------------------------------------- */ poSRS->Clear(); poSRS->SetGeocCS( CSLGetField( papszRecord, CSVGetFileFieldId(pszFilename, "COORD_REF_SYS_NAME")) ); /* -------------------------------------------------------------------- */ /* Get datum related information. */ /* -------------------------------------------------------------------- */ const int nDatumCode = atoi(CSLGetField( papszRecord, CSVGetFileFieldId(pszFilename, "DATUM_CODE"))); char *pszDatumName = CPLStrdup( CSLGetField(papszRecord, CSVGetFileFieldId(pszFilename, "DATUM_NAME")) ); OGREPSGDatumNameMassage( &pszDatumName ); const int nEllipsoidCode = atoi(CSLGetField( papszRecord, CSVGetFileFieldId(pszFilename, "ELLIPSOID_CODE"))); const int nPMCode = atoi(CSLGetField( papszRecord, CSVGetFileFieldId(pszFilename, "PRIME_MERIDIAN_CODE"))); /* -------------------------------------------------------------------- */ /* Get prime meridian information. */ /* -------------------------------------------------------------------- */ char *pszPMName = nullptr; double dfPMOffset = 0.0; if( !EPSGGetPMInfo( nPMCode, &pszPMName, &dfPMOffset ) ) { CPLFree( pszDatumName ); return OGRERR_UNSUPPORTED_SRS; } /* -------------------------------------------------------------------- */ /* Get the ellipsoid information. */ /* -------------------------------------------------------------------- */ char *pszEllipsoidName = nullptr; double dfSemiMajor = 0.0; double dfInvFlattening = 0.0; if( OSRGetEllipsoidInfo( nEllipsoidCode, &pszEllipsoidName, &dfSemiMajor, &dfInvFlattening ) != OGRERR_NONE ) { CPLFree( pszDatumName ); CPLFree( pszPMName ); return OGRERR_UNSUPPORTED_SRS; } /* -------------------------------------------------------------------- */ /* Setup the spheroid. */ /* -------------------------------------------------------------------- */ OGR_SRSNode *poSpheroid = new OGR_SRSNode( "SPHEROID" ); poSpheroid->AddChild( new OGR_SRSNode( pszEllipsoidName ) ); char szValue[128] = {}; OGRsnPrintDouble( szValue, sizeof(szValue), dfSemiMajor ); poSpheroid->AddChild( new OGR_SRSNode(szValue) ); OGRsnPrintDouble( szValue, sizeof(szValue), dfInvFlattening ); poSpheroid->AddChild( new OGR_SRSNode(szValue) ); CPLFree( pszEllipsoidName ); /* -------------------------------------------------------------------- */ /* Setup the Datum. */ /* -------------------------------------------------------------------- */ OGR_SRSNode *poDatum = new OGR_SRSNode( "DATUM" ); poDatum->AddChild( new OGR_SRSNode(pszDatumName) ); poDatum->AddChild( poSpheroid ); poSRS->GetRoot()->AddChild( poDatum ); CPLFree( pszDatumName ); /* -------------------------------------------------------------------- */ /* Setup the prime meridian. */ /* -------------------------------------------------------------------- */ if( dfPMOffset == 0.0 ) strcpy( szValue, "0" ); else OGRsnPrintDouble( szValue, sizeof(szValue), dfPMOffset ); OGR_SRSNode *poPM = new OGR_SRSNode( "PRIMEM" ); poPM->AddChild( new OGR_SRSNode( pszPMName ) ); poPM->AddChild( new OGR_SRSNode( szValue ) ); poSRS->GetRoot()->AddChild( poPM ); CPLFree( pszPMName ); /* -------------------------------------------------------------------- */ /* Should we try to lookup a datum transform? */ /* -------------------------------------------------------------------- */ #if 0 if( EPSGGetWGS84Transform( nGeogCS, adfBursaTransform ) ) { char szValue[100] = {}; OGR_SRSNode *poWGS84 = new OGR_SRSNode( "TOWGS84" ); for( int iCoeff = 0; iCoeff < 7; iCoeff++ ) { CPLsnprintf( szValue, sizeof(szValue), "%g", adfBursaTransform[iCoeff] ); poWGS84->AddChild( new OGR_SRSNode( szValue ) ); } poSRS->GetAttrNode( "DATUM" )->AddChild( poWGS84 ); } #endif /* -------------------------------------------------------------------- */ /* Set linear units. */ /* -------------------------------------------------------------------- */ int nUOMLength = atoi(CSLGetField( papszRecord, CSVGetFileFieldId(pszFilename, "UOM_CODE"))); double dfInMeters = 1.0; char *pszUOMLengthName = nullptr; if( !EPSGGetUOMLengthInfo( nUOMLength, &pszUOMLengthName, &dfInMeters ) ) { return OGRERR_UNSUPPORTED_SRS; } poSRS->SetLinearUnits( pszUOMLengthName, dfInMeters ); poSRS->SetAuthority( "GEOCCS|UNIT", "EPSG", nUOMLength ); CPLFree( pszUOMLengthName ); /* -------------------------------------------------------------------- */ /* Set axes */ /* -------------------------------------------------------------------- */ OGR_SRSNode *poAxis = new OGR_SRSNode( "AXIS" ); poAxis->AddChild( new OGR_SRSNode( "Geocentric X" ) ); poAxis->AddChild( new OGR_SRSNode( OSRAxisEnumToName(OAO_Other) ) ); poSRS->GetRoot()->AddChild( poAxis ); poAxis = new OGR_SRSNode( "AXIS" ); poAxis->AddChild( new OGR_SRSNode( "Geocentric Y" ) ); poAxis->AddChild( new OGR_SRSNode( OSRAxisEnumToName(OAO_Other) ) ); poSRS->GetRoot()->AddChild( poAxis ); poAxis = new OGR_SRSNode( "AXIS" ); poAxis->AddChild( new OGR_SRSNode( "Geocentric Z" ) ); poAxis->AddChild( new OGR_SRSNode( OSRAxisEnumToName(OAO_North) ) ); poSRS->GetRoot()->AddChild( poAxis ); /* -------------------------------------------------------------------- */ /* Set the authority codes. */ /* -------------------------------------------------------------------- */ poSRS->SetAuthority( "DATUM", "EPSG", nDatumCode ); poSRS->SetAuthority( "SPHEROID", "EPSG", nEllipsoidCode ); poSRS->SetAuthority( "PRIMEM", "EPSG", nPMCode ); poSRS->SetAuthority( "GEOCCS", "EPSG", nGCSCode ); return OGRERR_NONE; } /************************************************************************/ /* importFromEPSG() */ /************************************************************************/ /** * \brief Initialize SRS based on EPSG GCS or PCS code. * * This method will initialize the spatial reference based on the * passed in EPSG GCS or PCS code. The coordinate system definitions * are normally read from the EPSG derived support files such as * pcs.csv, gcs.csv, pcs.override.csv, gcs.override.csv and falling * back to search for a PROJ.4 epsg init file or a definition in epsg.wkt. * * These support files are normally searched for in /usr/local/share/gdal * or in the directory identified by the GDAL_DATA configuration option. * See CPLFindFile() for details. * * This method is relatively expensive, and generally involves quite a bit * of text file scanning. Reasonable efforts should be made to avoid calling * it many times for the same coordinate system. * * This method is similar to importFromEPSGA() except that EPSG preferred * axis ordering will *not* be applied for geographic coordinate systems. * EPSG normally defines geographic coordinate systems to use lat/long * contrary to typical GIS use). Since OGR 1.10.0, EPSG preferred * axis ordering will also *not* be applied for projected coordinate systems * that use northing/easting order. * * This method is the same as the C function OSRImportFromEPSG(). * * @param nCode a GCS or PCS code from the horizontal coordinate system table. * * @return OGRERR_NONE on success, or an error code on failure. */ OGRErr OGRSpatialReference::importFromEPSG( int nCode ) { OGRErr eErr = importFromEPSGA( nCode ); // Strip any GCS axis settings found. if( eErr == OGRERR_NONE ) { OGR_SRSNode *poGEOGCS = GetAttrNode( "GEOGCS" ); if( poGEOGCS != nullptr ) poGEOGCS->StripNodes( "AXIS" ); OGR_SRSNode *poPROJCS = GetAttrNode( "PROJCS" ); if( poPROJCS != nullptr && EPSGTreatsAsNorthingEasting() ) poPROJCS->StripNodes( "AXIS" ); } return eErr; } /************************************************************************/ /* OSRImportFromEPSG() */ /************************************************************************/ /** * \brief Initialize SRS based on EPSG GCS or PCS code. * * This function is the same as OGRSpatialReference::importFromEPSG(). */ OGRErr CPL_STDCALL OSRImportFromEPSG( OGRSpatialReferenceH hSRS, int nCode ) { VALIDATE_POINTER1( hSRS, "OSRImportFromEPSG", OGRERR_FAILURE ); return reinterpret_cast<OGRSpatialReference *>(hSRS)-> importFromEPSG( nCode ); } /************************************************************************/ /* importFromEPSGA() */ /************************************************************************/ /** * \brief Initialize SRS based on EPSG GCS or PCS code. * * This method will initialize the spatial reference based on the * passed in EPSG GCS or PCS code. * * This method is similar to importFromEPSG() except that EPSG preferred axis * ordering *will* be applied for geographic and projected coordinate systems. * EPSG normally defines geographic coordinate systems to use lat/long, and also * there are also a few projected coordinate systems that use northing/easting * order contrary to typical GIS use). See * OGRSpatialReference::importFromEPSG() for more details on operation of this * method. * * This method is the same as the C function OSRImportFromEPSGA(). * * @param nCode a GCS or PCS code from the horizontal coordinate system table. * * @return OGRERR_NONE on success, or an error code on failure. */ OGRErr OGRSpatialReference::importFromEPSGA( int nCode ) { return importFromEPSGAInternal(nCode, nullptr); } /************************************************************************/ /* importFromEPSGAInternal() */ /************************************************************************/ OGRErr OGRSpatialReference::importFromEPSGAInternal( int nCode, const char* pszSRSType ) { const int nCodeIn = nCode; // HACK to support 3D WGS84 if( nCode == 4979 ) nCode = 4326; bNormInfoSet = FALSE; /* -------------------------------------------------------------------- */ /* Clear any existing definition. */ /* -------------------------------------------------------------------- */ if( GetRoot() != nullptr ) { delete poRoot; poRoot = nullptr; } /* -------------------------------------------------------------------- */ /* Verify that we can find the required filename(s). */ /* -------------------------------------------------------------------- */ if( CSVScanFileByName( CSVFilename( "gcs.csv" ), "COORD_REF_SYS_CODE", "4269", CC_Integer ) == nullptr ) { CPLError( CE_Failure, CPLE_OpenFailed, "Unable to open EPSG support file %s. " "Try setting the GDAL_DATA environment variable to point to " "the directory containing EPSG csv files.", CSVFilename( "gcs.csv" ) ); return OGRERR_FAILURE; } /* -------------------------------------------------------------------- */ /* Try this as various sorts of objects till one works. */ /* -------------------------------------------------------------------- */ OGRErr eErr; if( pszSRSType && EQUAL(pszSRSType, "GEOGCS") ) { eErr = SetEPSGGeogCS( this, nCode ); if( eErr != OGRERR_NONE ) return eErr; } else if( pszSRSType && EQUAL(pszSRSType, "PROJCS") ) { eErr = SetEPSGProjCS( this, nCode ); if( eErr != OGRERR_NONE ) return eErr; } else { eErr = SetEPSGGeogCS( this, nCode ); } if( eErr == OGRERR_UNSUPPORTED_SRS ) eErr = SetEPSGProjCS( this, nCode ); if( eErr == OGRERR_UNSUPPORTED_SRS ) eErr = SetEPSGVertCS( this, nCode ); if( eErr == OGRERR_UNSUPPORTED_SRS ) eErr = SetEPSGCompdCS( this, nCode ); if( eErr == OGRERR_UNSUPPORTED_SRS ) eErr = SetEPSGGeocCS( this, nCode ); /* -------------------------------------------------------------------- */ /* If we get it as an unsupported code, try looking it up in */ /* the epsg.wkt coordinate system dictionary. */ /* -------------------------------------------------------------------- */ if( eErr == OGRERR_UNSUPPORTED_SRS ) { char szCode[32] = { 0 }; snprintf( szCode, sizeof(szCode), "%d", nCode ); eErr = importFromDict( "epsg.wkt", szCode ); } /* -------------------------------------------------------------------- */ /* If we get it as an unsupported code, try looking it up in */ /* the PROJ.4 support file(s). */ /* -------------------------------------------------------------------- */ if( eErr == OGRERR_UNSUPPORTED_SRS ) { char szWrkDefn[100] = {}; snprintf( szWrkDefn, sizeof(szWrkDefn), "+init=epsg:%d", nCode ); char *pszNormalized = OCTProj4Normalize( szWrkDefn ); if( strstr(pszNormalized, "proj=") != nullptr ) eErr = importFromProj4( pszNormalized ); CPLFree( pszNormalized ); } /* -------------------------------------------------------------------- */ /* Push in authority information if we were successful, and it */ /* is not already present. */ /* -------------------------------------------------------------------- */ const char *pszAuthName = nullptr; if( IsProjected() ) pszAuthName = GetAuthorityName( "PROJCS" ); else pszAuthName = GetAuthorityName( "GEOGCS" ); if( eErr == OGRERR_NONE && (pszAuthName == nullptr || nCode != nCodeIn) ) { if( IsProjected() ) SetAuthority( "PROJCS", "EPSG", nCodeIn ); else if( IsGeographic() ) SetAuthority( "GEOGCS", "EPSG", nCodeIn ); } /* -------------------------------------------------------------------- */ /* Otherwise officially issue an error message. */ /* -------------------------------------------------------------------- */ if( eErr == OGRERR_UNSUPPORTED_SRS ) { CPLError( CE_Failure, CPLE_NotSupported, "EPSG PCS/GCS code %d not found in EPSG support files. " "Is this a valid EPSG coordinate system?", nCode ); } /* -------------------------------------------------------------------- */ /* To the extent possible, we want to return the results in as */ /* close to standard OGC format as possible, so we fixup the */ /* ordering. */ /* -------------------------------------------------------------------- */ if( eErr == OGRERR_NONE ) { eErr = FixupOrdering(); } return eErr; } /************************************************************************/ /* OSRImportFromEPSGA() */ /************************************************************************/ /** * \brief Initialize SRS based on EPSG GCS or PCS code. * * This function is the same as OGRSpatialReference::importFromEPSGA(). */ OGRErr CPL_STDCALL OSRImportFromEPSGA( OGRSpatialReferenceH hSRS, int nCode ) { VALIDATE_POINTER1( hSRS, "OSRImportFromEPSGA", OGRERR_FAILURE ); return reinterpret_cast<OGRSpatialReference *>(hSRS)-> importFromEPSGA( nCode ); } /************************************************************************/ /* SetStatePlane() */ /************************************************************************/ /** * \brief Set State Plane projection definition. * * This will attempt to generate a complete definition of a state plane * zone based on generating the entire SRS from the EPSG tables. If the * EPSG tables are unavailable, it will produce a stubbed LOCAL_CS definition * and return OGRERR_FAILURE. * * This method is the same as the C function OSRSetStatePlaneWithUnits(). * * @param nZone State plane zone number, in the USGS numbering scheme (as * distinct from the Arc/Info and Erdas numbering scheme. * * @param bNAD83 TRUE if the NAD83 zone definition should be used or FALSE * if the NAD27 zone definition should be used. * * @param pszOverrideUnitName Linear unit name to apply overriding the * legal definition for this zone. * * @param dfOverrideUnit Linear unit conversion factor to apply overriding * the legal definition for this zone. * * @return OGRERR_NONE on success, or OGRERR_FAILURE on failure, mostly likely * due to the EPSG tables not being accessible. */ OGRErr OGRSpatialReference::SetStatePlane( int nZone, int bNAD83, const char *pszOverrideUnitName, double dfOverrideUnit ) { /* -------------------------------------------------------------------- */ /* Get the index id from stateplane.csv. */ /* -------------------------------------------------------------------- */ if( !bNAD83 && nZone > INT_MAX - 10000 ) return OGRERR_FAILURE; const int nAdjustedId = bNAD83 ? nZone : nZone + 10000; /* -------------------------------------------------------------------- */ /* Turn this into a PCS code. We assume there will only be one */ /* PCS corresponding to each Proj_ code since the proj code */ /* already effectively indicates NAD27 or NAD83. */ /* -------------------------------------------------------------------- */ char szID[32] = {}; snprintf( szID, sizeof(szID), "%d", nAdjustedId ); const int nPCSCode = atoi( CSVGetField( CSVFilename( "stateplane.csv" ), "ID", szID, CC_Integer, "EPSG_PCS_CODE" ) ); if( nPCSCode < 1 ) { static bool bFailureReported = false; if( !bFailureReported ) { bFailureReported = true; CPLError( CE_Warning, CPLE_OpenFailed, "Unable to find state plane zone in stateplane.csv, " "likely because the GDAL data files cannot be found. " "Using incomplete definition of state plane zone." ); } Clear(); if( bNAD83 ) { char szName[128] = {}; snprintf( szName, sizeof(szName), "State Plane Zone %d / NAD83", nZone ); SetLocalCS( szName ); SetLinearUnits( SRS_UL_METER, 1.0 ); } else { char szName[128] = {}; snprintf( szName, sizeof(szName), "State Plane Zone %d / NAD27", nZone ); SetLocalCS( szName ); SetLinearUnits( SRS_UL_US_FOOT, CPLAtof(SRS_UL_US_FOOT_CONV) ); } return OGRERR_FAILURE; } /* -------------------------------------------------------------------- */ /* Define based on a full EPSG definition of the zone. */ /* -------------------------------------------------------------------- */ OGRErr eErr = importFromEPSG( nPCSCode ); if( eErr != OGRERR_NONE ) return eErr; /* -------------------------------------------------------------------- */ /* Apply units override if required. */ /* */ /* We will need to adjust the linear projection parameter to */ /* match the provided units, and clear the authority code. */ /* -------------------------------------------------------------------- */ if( dfOverrideUnit != 0.0 && fabs(dfOverrideUnit - GetLinearUnits()) > 0.0000000001 ) { const double dfFalseEasting = GetNormProjParm( SRS_PP_FALSE_EASTING ); const double dfFalseNorthing = GetNormProjParm( SRS_PP_FALSE_NORTHING); SetLinearUnits( pszOverrideUnitName, dfOverrideUnit ); SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting ); SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing ); OGR_SRSNode * const poPROJCS = GetAttrNode( "PROJCS" ); if( poPROJCS != nullptr && poPROJCS->FindChild( "AUTHORITY" ) != -1 ) { poPROJCS->DestroyChild( poPROJCS->FindChild( "AUTHORITY" ) ); } } return OGRERR_NONE; } /************************************************************************/ /* OSRSetStatePlane() */ /************************************************************************/ /** * \brief Set State Plane projection definition. * * This function is the same as OGRSpatialReference::SetStatePlane(). */ OGRErr OSRSetStatePlane( OGRSpatialReferenceH hSRS, int nZone, int bNAD83 ) { VALIDATE_POINTER1( hSRS, "OSRSetStatePlane", OGRERR_FAILURE ); return reinterpret_cast<OGRSpatialReference *>(hSRS)-> SetStatePlane( nZone, bNAD83 ); } /************************************************************************/ /* OSRSetStatePlaneWithUnits() */ /************************************************************************/ /** * \brief Set State Plane projection definition. * * This function is the same as OGRSpatialReference::SetStatePlane(). */ OGRErr OSRSetStatePlaneWithUnits( OGRSpatialReferenceH hSRS, int nZone, int bNAD83, const char *pszOverrideUnitName, double dfOverrideUnit ) { VALIDATE_POINTER1( hSRS, "OSRSetStatePlaneWithUnits", OGRERR_FAILURE ); return reinterpret_cast<OGRSpatialReference *>(hSRS)-> SetStatePlane( nZone, bNAD83, pszOverrideUnitName, dfOverrideUnit ); } /************************************************************************/ /* GetEPSGGeogCS() */ /************************************************************************/ /** Try to establish what the EPSG code for this coordinate systems * GEOGCS might be. Returns -1 if no reasonable guess can be made. * * @return EPSG code */ // TODO: We really need to do some name lookups. int OGRSpatialReference::GetEPSGGeogCS() const { const char *pszAuthName = GetAuthorityName( "GEOGCS" ); /* -------------------------------------------------------------------- */ /* Do we already have it? */ /* -------------------------------------------------------------------- */ if( pszAuthName != nullptr && EQUAL(pszAuthName, "epsg") ) return atoi(GetAuthorityCode( "GEOGCS" )); /* -------------------------------------------------------------------- */ /* Get the datum and geogcs names. */ /* -------------------------------------------------------------------- */ const char *pszGEOGCS = GetAttrValue( "GEOGCS" ); const char *pszDatum = GetAttrValue( "DATUM" ); // We can only operate on coordinate systems with a geogcs. if( pszGEOGCS == nullptr || pszDatum == nullptr ) return -1; /* -------------------------------------------------------------------- */ /* Is this a "well known" geographic coordinate system? */ /* -------------------------------------------------------------------- */ const bool bWGS = strstr(pszGEOGCS, "WGS") != nullptr || strstr(pszDatum, "WGS") || strstr(pszGEOGCS, "World Geodetic System") || strstr(pszGEOGCS, "World_Geodetic_System") || strstr(pszDatum, "World Geodetic System") || strstr(pszDatum, "World_Geodetic_System"); const bool bNAD = strstr(pszGEOGCS, "NAD") != nullptr || strstr(pszDatum, "NAD") || strstr(pszGEOGCS, "North American") || strstr(pszGEOGCS, "North_American") || strstr(pszDatum, "North American") || strstr(pszDatum, "North_American"); if( bWGS && (strstr(pszGEOGCS, "84") || strstr(pszDatum, "84")) ) return 4326; if( bWGS && (strstr(pszGEOGCS, "72") || strstr(pszDatum, "72")) ) return 4322; if( bNAD && (strstr(pszGEOGCS, "83") || strstr(pszDatum, "83")) ) return 4269; if( bNAD && (strstr(pszGEOGCS, "27") || strstr(pszDatum, "27")) ) return 4267; /* -------------------------------------------------------------------- */ /* If we know the datum, associate the most likely GCS with */ /* it. */ /* -------------------------------------------------------------------- */ pszAuthName = GetAuthorityName( "GEOGCS|DATUM" ); if( pszAuthName != nullptr && EQUAL(pszAuthName, "epsg") && GetPrimeMeridian() == 0.0 ) { const int nDatum = atoi(GetAuthorityCode("GEOGCS|DATUM")); if( nDatum >= 6000 && nDatum <= 6999 ) return nDatum - 2000; } return -1; } /************************************************************************/ /* AutoIdentifyEPSG() */ /************************************************************************/ /** * \brief Set EPSG authority info if possible. * * This method inspects a WKT definition, and adds EPSG authority nodes * where an aspect of the coordinate system can be easily and safely * corresponded with an EPSG identifier. In practice, this method will * evolve over time. In theory it can add authority nodes for any object * (i.e. spheroid, datum, GEOGCS, units, and PROJCS) that could have an * authority node. Mostly this is useful to inserting appropriate * PROJCS codes for common formulations (like UTM n WGS84). * * If it success the OGRSpatialReference is updated in place, and the * method return OGRERR_NONE. If the method fails to identify the * general coordinate system OGRERR_UNSUPPORTED_SRS is returned but no * error message is posted via CPLError(). * * This method is the same as the C function OSRAutoIdentifyEPSG(). * * Since GDAL 2.3, the FindMatches() method can also be used for improved * matching by researching the EPSG catalog. * * @return OGRERR_NONE or OGRERR_UNSUPPORTED_SRS. */ OGRErr OGRSpatialReference::AutoIdentifyEPSG() { /* -------------------------------------------------------------------- */ /* Do we have a GEOGCS node, but no authority? If so, try */ /* guessing it. */ /* -------------------------------------------------------------------- */ if( (IsProjected() || IsGeographic()) && GetAuthorityCode( "GEOGCS" ) == nullptr ) { const int nGCS = GetEPSGGeogCS(); if( nGCS != -1 ) SetAuthority( "GEOGCS", "EPSG", nGCS ); } if( IsProjected() && GetAuthorityCode( "PROJCS") == nullptr ) { const char *pszProjection = GetAttrValue( "PROJECTION" ); /* -------------------------------------------------------------------- */ /* Is this a UTM coordinate system with a common GEOGCS? */ /* -------------------------------------------------------------------- */ int nZone = 0; int bNorth = FALSE; if( (nZone = GetUTMZone( &bNorth )) != 0 ) { const char *pszAuthName = GetAuthorityName( "PROJCS|GEOGCS" ); const char *pszAuthCode = GetAuthorityCode( "PROJCS|GEOGCS" ); if( pszAuthName == nullptr || pszAuthCode == nullptr ) { // Don't exactly recognise datum. } else if( EQUAL(pszAuthName, "EPSG") && atoi(pszAuthCode) == 4326 ) { // WGS84 if( bNorth ) SetAuthority( "PROJCS", "EPSG", 32600 + nZone ); else SetAuthority( "PROJCS", "EPSG", 32700 + nZone ); } else if( EQUAL(pszAuthName, "EPSG") && atoi(pszAuthCode) == 4267 && nZone >= 3 && nZone <= 22 && bNorth ) { SetAuthority( "PROJCS", "EPSG", 26700 + nZone ); // NAD27 } else if( EQUAL(pszAuthName, "EPSG") && atoi(pszAuthCode) == 4269 && nZone >= 3 && nZone <= 23 && bNorth ) { SetAuthority( "PROJCS", "EPSG", 26900 + nZone ); // NAD83 } else if( EQUAL(pszAuthName, "EPSG") && atoi(pszAuthCode) == 4322 ) { // WGS72 if( bNorth ) SetAuthority( "PROJCS", "EPSG", 32200 + nZone ); else SetAuthority( "PROJCS", "EPSG", 32300 + nZone ); } } /* -------------------------------------------------------------------- */ /* Is this a Polar Stereographic system on WGS 84 ? */ /* -------------------------------------------------------------------- */ else if ( pszProjection != nullptr && EQUAL(pszProjection, SRS_PT_POLAR_STEREOGRAPHIC) ) { const char *pszAuthName = GetAuthorityName( "PROJCS|GEOGCS" ); const char *pszAuthCode = GetAuthorityCode( "PROJCS|GEOGCS" ); const double dfLatOrigin = GetNormProjParm( SRS_PP_LATITUDE_OF_ORIGIN, 0.0 ); if( pszAuthName != nullptr && EQUAL(pszAuthName, "EPSG") && pszAuthCode != nullptr && atoi(pszAuthCode) == 4326 && fabs( fabs(dfLatOrigin ) - 71.0 ) < 1e-15 && fabs(GetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, 0.0 )) < 1e-15 && fabs(GetProjParm( SRS_PP_SCALE_FACTOR, 1.0 ) - 1.0) < 1e-15 && fabs(GetNormProjParm( SRS_PP_FALSE_EASTING, 0.0 )) < 1e-15 && fabs(GetNormProjParm( SRS_PP_FALSE_NORTHING, 0.0 )) < 1e-15 && fabs(GetLinearUnits() - 1.0) < 1e-15 ) { if( dfLatOrigin > 0 ) // Arctic Polar Stereographic SetAuthority( "PROJCS", "EPSG", 3995 ); else // Antarctic Polar Stereographic SetAuthority( "PROJCS", "EPSG", 3031 ); } } } /* -------------------------------------------------------------------- */ /* Return. */ /* -------------------------------------------------------------------- */ if( IsProjected() && GetAuthorityCode("PROJCS") != nullptr ) return OGRERR_NONE; if( IsGeographic() && GetAuthorityCode("GEOGCS") != nullptr ) return OGRERR_NONE; return OGRERR_UNSUPPORTED_SRS; } /************************************************************************/ /* OSRAutoIdentifyEPSG() */ /************************************************************************/ /** * \brief Set EPSG authority info if possible. * * This function is the same as OGRSpatialReference::AutoIdentifyEPSG(). * * Since GDAL 2.3, the OSRFindMatches() function can also be used for improved * matching by researching the EPSG catalog. * */ OGRErr OSRAutoIdentifyEPSG( OGRSpatialReferenceH hSRS ) { VALIDATE_POINTER1( hSRS, "OSRAutoIdentifyEPSG", OGRERR_FAILURE ); return reinterpret_cast<OGRSpatialReference *>(hSRS)->AutoIdentifyEPSG(); } /************************************************************************/ /* EPSGTreatsAsLatLong() */ /************************************************************************/ /** * \brief This method returns TRUE if EPSG feels this geographic coordinate * system should be treated as having lat/long coordinate ordering. * * Currently this returns TRUE for all geographic coordinate systems * with an EPSG code set, and AXIS values set defining it as lat, long. * Note that coordinate systems with an EPSG code and no axis settings * will be assumed to not be lat/long. * * FALSE will be returned for all coordinate systems that are not geographic, * or that do not have an EPSG code set. * * This method is the same as the C function OSREPSGTreatsAsLatLong(). * * @return TRUE or FALSE. */ int OGRSpatialReference::EPSGTreatsAsLatLong() const { if( !IsGeographic() ) return FALSE; const char *pszAuth = GetAuthorityName( "GEOGCS" ); if( pszAuth == nullptr || !EQUAL(pszAuth, "EPSG") ) return FALSE; const OGR_SRSNode * const poFirstAxis = GetAttrNode( "GEOGCS|AXIS" ); if( poFirstAxis == nullptr ) return FALSE; if( poFirstAxis->GetChildCount() >= 2 && EQUAL(poFirstAxis->GetChild(1)->GetValue(), "NORTH") ) return TRUE; return FALSE; } /************************************************************************/ /* OSREPSGTreatsAsLatLong() */ /************************************************************************/ /** * \brief This function returns TRUE if EPSG feels this geographic coordinate * system should be treated as having lat/long coordinate ordering. * * This function is the same as OGRSpatialReference::OSREPSGTreatsAsLatLong(). */ int OSREPSGTreatsAsLatLong( OGRSpatialReferenceH hSRS ) { VALIDATE_POINTER1( hSRS, "OSREPSGTreatsAsLatLong", OGRERR_FAILURE ); return reinterpret_cast<OGRSpatialReference *>(hSRS)->EPSGTreatsAsLatLong(); } /************************************************************************/ /* EPSGTreatsAsNorthingEasting() */ /************************************************************************/ /** * \brief This method returns TRUE if EPSG feels this projected coordinate * system should be treated as having northing/easting coordinate ordering. * * Currently this returns TRUE for all projected coordinate systems * with an EPSG code set, and AXIS values set defining it as northing, easting. * * FALSE will be returned for all coordinate systems that are not projected, * or that do not have an EPSG code set. * * This method is the same as the C function EPSGTreatsAsNorthingEasting(). * * @return TRUE or FALSE. * * @since OGR 1.10.0 */ int OGRSpatialReference::EPSGTreatsAsNorthingEasting() const { if( !IsProjected() ) return FALSE; const char *pszAuth = GetAuthorityName( "PROJCS" ); if( pszAuth == nullptr || !EQUAL(pszAuth, "EPSG") ) return FALSE; const OGR_SRSNode * const poFirstAxis = GetAttrNode( "PROJCS|AXIS" ); if( poFirstAxis == nullptr ) return FALSE; if( poFirstAxis->GetChildCount() >= 2 && EQUAL(poFirstAxis->GetChild(1)->GetValue(), "NORTH") ) return TRUE; return FALSE; } /************************************************************************/ /* OSREPSGTreatsAsNorthingEasting() */ /************************************************************************/ /** * \brief This function returns TRUE if EPSG feels this geographic coordinate * system should be treated as having northing/easting coordinate ordering. * * This function is the same as * OGRSpatialReference::EPSGTreatsAsNorthingEasting(). * * @since OGR 1.10.0 */ int OSREPSGTreatsAsNorthingEasting( OGRSpatialReferenceH hSRS ) { VALIDATE_POINTER1( hSRS, "OSREPSGTreatsAsNorthingEasting", OGRERR_FAILURE ); return reinterpret_cast<OGRSpatialReference *>(hSRS)-> EPSGTreatsAsNorthingEasting(); } /************************************************************************/ /* CleanupFindMatchesCacheAndMutex() */ /************************************************************************/ void CleanupFindMatchesCacheAndMutex() { if( hFindMatchesMutex != nullptr ) { CPLDestroyMutex(hFindMatchesMutex); hFindMatchesMutex = nullptr; } if( papoSRSCache_GEOGCS ) { for( size_t i = 0; i < papoSRSCache_GEOGCS->size(); i++ ) delete (*papoSRSCache_GEOGCS)[i]; delete papoSRSCache_GEOGCS; papoSRSCache_GEOGCS = nullptr; } if( papoSRSCache_PROJCS ) { for( size_t i = 0; i < papoSRSCache_PROJCS->size(); i++ ) delete (*papoSRSCache_PROJCS)[i]; delete papoSRSCache_PROJCS; papoSRSCache_PROJCS = nullptr; } delete poMapESRIPROJCSNameToEPSGCode; poMapESRIPROJCSNameToEPSGCode = nullptr; delete poMapESRIGEOGCSNameToEPSGCode; poMapESRIGEOGCSNameToEPSGCode = nullptr; } /************************************************************************/ /* MassageSRSName() */ /************************************************************************/ /* Transform a SRS name typically coming from EPSG or ESRI into a simplified * form that can be compared. */ static CPLString MassageSRSName(const char* pszInput, bool bExtraMassaging) { CPLString osRet; bool bLastWasSep = false; for( int i = 0; pszInput[i] != '\0'; ++ i) { if( isdigit( pszInput[i] ) ) { if( (i > 0 && isalpha( pszInput[i-1] )) || bLastWasSep ) osRet += "_"; bLastWasSep = false; /* Abbreviate 19xx as xx */ if( pszInput[i] == '1' && pszInput[i+1] == '9' && pszInput[i+2] != '\0' && isdigit(pszInput[i+2]) && (i == 0 || !isdigit(pszInput[i-1])) ) { i ++; continue; } osRet += pszInput[i]; } else if( isalpha( pszInput[i] ) ) { if( bLastWasSep ) osRet += "_"; osRet += pszInput[i]; bLastWasSep = false; } else { bLastWasSep = true; } } osRet.tolower(); osRet.replaceAll("gauss_kruger", "gk"); // EPSG -> ESRI osRet.replaceAll("rt_90_25", "rt_90_2_5"); // ESRI -> EPSG osRet.replaceAll("rt_38_25", "rt_38_2_5"); // ESRI -> EPSG osRet.replaceAll("_zone_", "_"); // EPSG -> ESRI osRet.replaceAll("_stateplane_", "_"); // ESRI -> EPSG osRet.replaceAll("_nsidc_", "_"); // EPSG -> ESRI osRet.replaceAll("_I_", "_1_"); // ESRI -> EPSG osRet.replaceAll("_II_", "_2_"); // ESRI -> EPSG osRet.replaceAll("_III_", "_3_"); // ESRI -> EPSG osRet.replaceAll("_IV_", "_4_"); // ESRI -> EPSG osRet.replaceAll("_V_", "_5_"); // ESRI -> EPSG osRet.replaceAll("pulkovo_42_adj_83_", "pulkovo_42_83_"); // ESRI -> EPSG osRet.replaceAll("_old_fips", "_deprecated_fips"); if( bExtraMassaging ) osRet.replaceAll("_deprecated", ""); // EPSG -> ESRI // _FIPS_XXXX_Feet --> _ftUS ESRI -> EPSG // _FIPS_XXXX_Ft_US --> _ftUS ESRI -> EPSG // _FIPS_XXXX --> "" ESRI -> EPSG size_t nPos = osRet.find("_fips_"); if( nPos != std::string::npos ) { size_t nPos2 = osRet.find("_feet", nPos + strlen("_fips_")); if( nPos2 != std::string::npos && nPos2 + strlen("_feet") == osRet.size() ) { osRet.resize(nPos); osRet += "_ftus"; } else { nPos2 = osRet.find("_ft_us", nPos + strlen("_fips_")); if( nPos2 != std::string::npos && nPos2 + strlen("_ft_us") == osRet.size() ) { osRet.resize(nPos); osRet += "_ftus"; } else if( osRet.find('_', nPos + strlen("_fips_")) == std::string::npos ) { osRet.resize(nPos); } } } return osRet; } /************************************************************************/ /* IngestDict() */ /************************************************************************/ static void IngestDict(const char* pszDictFile, const char* pszSRSType, std::vector<OGRSpatialReference*>* papoSRSCache, VSILFILE* fpOut) { /* -------------------------------------------------------------------- */ /* Find and open file. */ /* -------------------------------------------------------------------- */ const char *pszFilename = CPLFindFile( "gdal", pszDictFile ); if( pszFilename == nullptr ) return; VSILFILE *fp = VSIFOpenL( pszFilename, "rb" ); if( fp == nullptr ) return; if( fpOut ) { VSIFPrintfL(fpOut, "# From %s\n", pszDictFile); } /* -------------------------------------------------------------------- */ /* Process lines. */ /* -------------------------------------------------------------------- */ const char *pszLine = nullptr; while( (pszLine = CPLReadLineL(fp)) != nullptr ) { if( pszLine[0] == '#' ) continue; const char* pszComma = strchr(pszLine, ','); if( pszComma ) { const char* pszWKT = pszComma + 1; if( STARTS_WITH(pszWKT, pszSRSType) ) { OGRSpatialReference* poSRS = new OGRSpatialReference(); if( poSRS->SetFromUserInput(pszWKT) == OGRERR_NONE ) { const char *pszProjection = poSRS->GetAttrValue( "PROJECTION" ); if( pszProjection && EQUAL(pszProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP) ) { // Remove duplicate Standard_Parallel_1 double dfLatOrigin = poSRS->GetProjParm(SRS_PP_LATITUDE_OF_ORIGIN); double dfStdParallel1 = poSRS->GetProjParm(SRS_PP_STANDARD_PARALLEL_1); if( dfLatOrigin == dfStdParallel1 ) { OGR_SRSNode *poPROJCS = poSRS->GetAttrNode( "PROJCS" ); if( poPROJCS ) { const int iChild = poSRS->FindProjParm( SRS_PP_STANDARD_PARALLEL_1, poPROJCS ); if( iChild != -1 ) poPROJCS->DestroyChild( iChild); } } } poSRS->morphFromESRI(); papoSRSCache->push_back(poSRS); if( fpOut ) { VSIFPrintfL(fpOut, "%s\n", pszWKT); } } else { delete poSRS; } } } } /* -------------------------------------------------------------------- */ /* Cleanup */ /* -------------------------------------------------------------------- */ VSIFCloseL( fp ); } /************************************************************************/ /* BuildESRICSNameCache() */ /************************************************************************/ static void BuildESRICSNameCache(const char* pszSRSType, std::map<CPLString, int>* poMapCSNameToCode, VSILFILE* fpOut) { const char *pszFilename = CPLFindFile( "gdal", "esri_epsg.wkt" ); if( pszFilename == nullptr ) return; VSILFILE *fp = VSIFOpenL( pszFilename, "rb" ); if( fp == nullptr ) return; /* -------------------------------------------------------------------- */ /* Process lines. */ /* -------------------------------------------------------------------- */ const char *pszLine = nullptr; while( (pszLine = CPLReadLineL(fp)) != nullptr ) { if( pszLine[0] == '#' ) continue; const char* pszComma = strchr(pszLine, ','); if( pszComma ) { const char* pszWKT = pszComma + 1; if( STARTS_WITH(pszWKT, pszSRSType) ) { OGRSpatialReference oSRS; if( oSRS.SetFromUserInput(pszWKT) == OGRERR_NONE ) { const char* pszSRSName = oSRS.GetAttrValue(pszSRSType); const char* pszAuthCode = oSRS.GetAuthorityCode(nullptr); if( pszSRSName && pszAuthCode ) { (*poMapCSNameToCode)[pszSRSName] = atoi(pszAuthCode); VSIFPrintfL(fpOut, "%s,%s\n", pszSRSName, pszAuthCode); } } } } } VSIFCloseL( fp ); } /************************************************************************/ /* GetSRSCache() */ /************************************************************************/ const std::vector<OGRSpatialReference*>* OGRSpatialReference::GetSRSCache( const char* pszSRSType, const std::map<CPLString, int>*& poMapCSNameToCodeOut) { if( pszSRSType == nullptr ) return nullptr; std::vector<OGRSpatialReference*>* papoSRSCache = nullptr; std::map<CPLString, int>* poMapCSNameToCode = nullptr; CPLMutexHolderD(&hFindMatchesMutex); CPLString osFilename; const char* pszFilename = ""; if( EQUAL(pszSRSType, "PROJCS") ) { pszFilename = "pcs.csv"; if( papoSRSCache_PROJCS != nullptr ) return papoSRSCache_PROJCS; papoSRSCache_PROJCS = new std::vector<OGRSpatialReference*>(); papoSRSCache = papoSRSCache_PROJCS; poMapESRIPROJCSNameToEPSGCode = new std::map<CPLString, int>(); poMapCSNameToCode = poMapESRIPROJCSNameToEPSGCode; } else if( EQUAL(pszSRSType, "GEOGCS") ) { pszFilename = "gcs.csv" ; if( papoSRSCache_GEOGCS != nullptr ) return papoSRSCache_GEOGCS; papoSRSCache_GEOGCS = new std::vector<OGRSpatialReference*>(); papoSRSCache = papoSRSCache_GEOGCS; poMapESRIGEOGCSNameToEPSGCode = new std::map<CPLString, int>(); poMapCSNameToCode = poMapESRIGEOGCSNameToEPSGCode; } else { return nullptr; } poMapCSNameToCodeOut = poMapCSNameToCode; // First try to look an already built SRS cache in ~/.gdal/X.Y/srs_cache const char* pszHome = CPLGetHomeDir(); const char* pszCSVFilename = CSVFilename(pszFilename); CPLString osCacheFilename; CPLString osCacheDirectory = CPLGetConfigOption("OSR_SRS_CACHE_DIRECTORY", ""); if( (pszHome != nullptr || !osCacheDirectory.empty()) && CPLTestBool(CPLGetConfigOption("OSR_SRS_CACHE", "YES")) ) { if( osCacheDirectory.empty() ) { osCacheDirectory = CPLFormFilename(pszHome, ".gdal", nullptr); // Version this, because might be sensitive to GDAL / // EPSG versions osCacheDirectory = CPLFormFilename(osCacheDirectory, CPLSPrintf("%d.%d", GDAL_VERSION_MAJOR, GDAL_VERSION_MINOR), nullptr); osCacheDirectory = CPLFormFilename(osCacheDirectory, "srs_cache", nullptr); } osCacheFilename = CPLFormFilename(osCacheDirectory, CPLResetExtension(pszFilename, "wkt"), nullptr); VSIStatBufL sStatCache; VSIStatBufL sStatCSV; VSILFILE* fp = nullptr; VSILFILE* fpESRINames = nullptr; if( VSIStatL((osCacheFilename + ".gz").c_str(), &sStatCache) == 0 && VSIStatL(pszCSVFilename, &sStatCSV) == 0 && sStatCache.st_mtime >= sStatCSV.st_mtime && (VSIStatL(CPLResetExtension( pszCSVFilename, "override.csv"), &sStatCSV) != 0 || sStatCache.st_mtime >= sStatCSV.st_mtime) ) { fp = VSIFOpenL(("/vsigzip/" + osCacheFilename + ".gz").c_str(), "rb"); if( fp ) { fpESRINames = VSIFOpenL( (CPLString("/vsigzip/") + CPLResetExtension(osCacheFilename, "esri.gz")).c_str(), "rb"); } } if( fp ) { CPLDebug("OSR", "Using %s cache", osCacheFilename.c_str()); const char* pszLine; while( (pszLine = CPLReadLineL(fp)) != nullptr ) { OGRSpatialReference* poSRS = new OGRSpatialReference(); poSRS->SetFromUserInput(pszLine); papoSRSCache->push_back(poSRS); const char* pszSRSName = poSRS->GetAttrValue(pszSRSType); const char* pszAuthCode = poSRS->GetAuthorityCode(nullptr); if( pszSRSName && pszAuthCode ) { (*poMapCSNameToCode)[pszSRSName] = atoi(pszAuthCode); } } VSIFCloseL(fp); if( fpESRINames ) { while( (pszLine = CPLReadLineL(fpESRINames)) != nullptr ) { const char* pszComma = strchr(pszLine, ','); if( pszComma ) { CPLString osName(pszLine); osName.resize(pszComma - pszLine); (*poMapCSNameToCode)[osName] = atoi(pszComma + 1); } } VSIFCloseL(fpESRINames); } return papoSRSCache; } } // If no already built cache, ingest the EPSG database and write the // cache CPLDebug("OSR", "Building %s cache", pszSRSType); VSILFILE* fp = VSIFOpenL(pszCSVFilename, "rb"); if( fp == nullptr ) { return nullptr; } VSILFILE* fpOut = nullptr; if( !osCacheFilename.empty() ) { CPLString osDirname(CPLGetDirname(osCacheFilename)); CPLString osDirnameParent(CPLGetDirname(osDirname)); CPLString osDirnameGrantParent( CPLGetDirname(osDirnameParent)); VSIMkdir( osDirnameGrantParent, 0755 ); VSIMkdir( osDirnameParent, 0755 ); VSIMkdir( osDirname, 0755 ); fpOut = VSIFOpenL( ("/vsigzip/" + osCacheFilename + ".gz").c_str(), "wb"); if( fpOut != nullptr ) { VSIFPrintfL(fpOut, "# From %s\n", pszFilename); } } const char* pszLine; OGRSpatialReference* poSRS = nullptr; CPLPushErrorHandler(CPLQuietErrorHandler); while( (pszLine = CPLReadLineL(fp)) != nullptr ) { if( poSRS == nullptr ) poSRS = new OGRSpatialReference(); int nCode = atoi(pszLine); if( nCode > 0 && poSRS->importFromEPSGAInternal(nCode, pszSRSType) == OGRERR_NONE ) { // Strip AXIS like in importFromEPSG() OGR_SRSNode *poGEOGCS = poSRS->GetAttrNode( "GEOGCS" ); if( poGEOGCS != nullptr ) poGEOGCS->StripNodes( "AXIS" ); OGR_SRSNode *poPROJCS = poSRS->GetAttrNode( "PROJCS" ); if( poPROJCS != nullptr && poSRS->EPSGTreatsAsNorthingEasting() ) poPROJCS->StripNodes( "AXIS" ); if( fpOut ) { char* pszWKT = nullptr; poSRS->exportToWkt(&pszWKT); if( pszWKT ) { VSIFPrintfL(fpOut, "%s\n", pszWKT); CPLFree(pszWKT); } } papoSRSCache->push_back(poSRS); const char* pszSRSName = poSRS->GetAttrValue(pszSRSType); if( pszSRSName ) { (*poMapCSNameToCode)[pszSRSName] = nCode; } poSRS = nullptr; } } CPLPopErrorHandler(); delete poSRS; VSIFCloseL(fp); IngestDict("esri_extra.wkt", pszSRSType, papoSRSCache, fpOut); if( fpOut ) VSIFCloseL(fpOut); fpOut = VSIFOpenL( (CPLString("/vsigzip/") + CPLResetExtension(osCacheFilename, "esri.gz")).c_str(), "wb"); if( fpOut != nullptr ) { BuildESRICSNameCache(pszSRSType, poMapCSNameToCode, fpOut); VSIFCloseL(fpOut); } return papoSRSCache; } /************************************************************************/ /* FindMatches() */ /************************************************************************/ /** * \brief Try to identify a match between the passed SRS and a related SRS * in a catalog (currently EPSG only) * * Matching may be partial, or may fail. * Returned entries will be sorted by decreasing match confidence (first * entry has the highest match confidence). * * The exact way matching is done may change in future versions. * * The current algorithm is: * - try first AutoIdentifyEPSG(). If it succeeds, return the corresponding SRS * - otherwise iterate over all SRS from the EPSG catalog (as found in GDAL * pcs.csv and gcs.csv files+esri_extra.wkt), and find those that match the * input SRS using the IsSame() function (ignoring TOWGS84 clauses) * - if there is a single match using IsSame() or one of the matches has the * same SRS name, return it with 100% confidence * - if a SRS has the same SRS name, but does not pass the IsSame() criteria, * return it with 50% confidence. * - otherwise return all candidate SRS that pass the IsSame() criteria with a * 90% confidence. * * A pre-built SRS cache in ~/.gdal/X.Y/srs_cache will be used if existing, * otherwise it will be built at the first run of this function. * * This method is the same as OSRFindMatches(). * * @param papszOptions NULL terminated list of options or NULL * @param pnEntries Output parameter. Number of values in the returned array. * @param ppanMatchConfidence Output parameter (or NULL). *ppanMatchConfidence * will be allocated to an array of *pnEntries whose values between 0 and 100 * indicate the confidence in the match. 100 is the highest confidence level. * The array must be freed with CPLFree(). * * @return an array of SRS that match the passed SRS, or NULL. Must be freed with * OSRFreeSRSArray() * * @since GDAL 2.3 */ OGRSpatialReferenceH* OGRSpatialReference::FindMatches( char** papszOptions, int* pnEntries, int** ppanMatchConfidence ) const { CPL_IGNORE_RET_VAL(papszOptions); if( pnEntries ) *pnEntries = 0; if( ppanMatchConfidence ) *ppanMatchConfidence = nullptr; OGRSpatialReference oSRSClone(*this); if( oSRSClone.AutoIdentifyEPSG() == OGRERR_NONE ) { const char* pszCode = oSRSClone.GetAuthorityCode(nullptr); if( pszCode ) oSRSClone.importFromEPSG(atoi(pszCode)); OGRSpatialReferenceH* pahRet = static_cast<OGRSpatialReferenceH*>( CPLCalloc(sizeof(OGRSpatialReferenceH), 2)); pahRet[0] = reinterpret_cast<OGRSpatialReferenceH>(oSRSClone.Clone()); if( pnEntries ) *pnEntries = 1; if( ppanMatchConfidence ) { *ppanMatchConfidence = static_cast<int*>(CPLMalloc(sizeof(int))); (*ppanMatchConfidence)[0] = 100; } return pahRet; } const char* pszSRSType = ""; if( IsProjected() ) { pszSRSType = "PROJCS"; } else if( IsGeographic() ) { pszSRSType = "GEOGCS"; } else { return nullptr; } const char*pszSRSName = GetAttrValue(pszSRSType); if( pszSRSName == nullptr ) return nullptr; const std::map<CPLString, int>* poMapCSNameToCode = nullptr; const std::vector<OGRSpatialReference*>* papoSRSCache = GetSRSCache(pszSRSType, poMapCSNameToCode); if( papoSRSCache == nullptr ) return nullptr; // If we have an exact match with a coordinate system name coming from // EPSG entries (either ours or ESRI), and the SRS are equivalent, then // use that exact match const char* apszOptions[] = { "TOWGS84=ONLY_IF_IN_BOTH", nullptr }; if( poMapCSNameToCode ) { auto oIter = poMapCSNameToCode->find(pszSRSName); if( oIter != poMapCSNameToCode->end() ) { OGRSpatialReference oSRS; if( oSRS.importFromEPSG(oIter->second) == OGRERR_NONE ) { if( IsSame(&oSRS, apszOptions) ) { OGRSpatialReferenceH* pahRet = static_cast<OGRSpatialReferenceH*>( CPLCalloc(sizeof(OGRSpatialReferenceH), 2)); pahRet[0] = reinterpret_cast<OGRSpatialReferenceH>( oSRS.Clone()); if( pnEntries ) *pnEntries = 1; if( ppanMatchConfidence ) { *ppanMatchConfidence = static_cast<int*>( CPLMalloc(sizeof(int))); (*ppanMatchConfidence)[0] = 100; } return pahRet; } } } } std::vector< OGRSpatialReference* > apoSameSRS; CPLString osSRSName(MassageSRSName(pszSRSName, false)); CPLString osSRSNameExtra(MassageSRSName(osSRSName, true)); std::vector<size_t> anMatchingSRSNameIndices; for(size_t i = 0; i < papoSRSCache->size(); i++ ) { const OGRSpatialReference* poOtherSRS = (*papoSRSCache)[i]; #ifdef notdef if( EQUAL(poOtherSRS->GetAuthorityCode(nullptr), "2765") ) { printf("brkpt\n"); /* ok */ } #endif const char* pszOtherSRSName = poOtherSRS->GetAttrValue(pszSRSType); if( pszOtherSRSName == nullptr ) continue; CPLString osOtherSRSName( MassageSRSName(pszOtherSRSName, false)); if( EQUAL( osSRSName, osOtherSRSName ) ) { anMatchingSRSNameIndices.push_back(i); } if( IsSame(poOtherSRS, apszOptions) ) { apoSameSRS.push_back( poOtherSRS->Clone() ); } } const size_t nSameCount = apoSameSRS.size(); if( nSameCount == 1 ) { OGRSpatialReferenceH* pahRet = static_cast<OGRSpatialReferenceH*>( CPLCalloc(sizeof(OGRSpatialReferenceH), 2)); pahRet[0] = reinterpret_cast<OGRSpatialReferenceH>(apoSameSRS[0]); if( pnEntries ) *pnEntries = 1; if( ppanMatchConfidence ) { *ppanMatchConfidence = static_cast<int*>(CPLMalloc(sizeof(int))); (*ppanMatchConfidence)[0] = 100; } return pahRet; } int nCountExtraMatches = 0; size_t iExtraMatch = 0; for(size_t i=0; i<nSameCount; i++) { CPLString osOtherSRSName( MassageSRSName(apoSameSRS[i]->GetAttrValue(pszSRSType), false)); CPLString osOtherSRSNameExtra(MassageSRSName(osOtherSRSName, true)); if( EQUAL(osSRSName, osOtherSRSName) ) { OGRSpatialReferenceH* pahRet = static_cast<OGRSpatialReferenceH*>( CPLCalloc(sizeof(OGRSpatialReferenceH), 2)); pahRet[0] = reinterpret_cast<OGRSpatialReferenceH>(apoSameSRS[i]); if( pnEntries ) *pnEntries = 1; if( ppanMatchConfidence ) { *ppanMatchConfidence = static_cast<int*>(CPLMalloc(sizeof(int))); (*ppanMatchConfidence)[0] = 100; } for(size_t j=0; j<nSameCount; j++) { if( i != j ) delete apoSameSRS[j]; } return pahRet; } else if ( EQUAL(osSRSNameExtra, osOtherSRSNameExtra) ) { nCountExtraMatches ++; iExtraMatch = i; } } if( nCountExtraMatches == 1 ) { OGRSpatialReferenceH* pahRet = static_cast<OGRSpatialReferenceH*>( CPLCalloc(sizeof(OGRSpatialReferenceH), 2)); pahRet[0] = reinterpret_cast<OGRSpatialReferenceH>(apoSameSRS[iExtraMatch]); if( pnEntries ) *pnEntries = 1; if( ppanMatchConfidence ) { *ppanMatchConfidence = static_cast<int*>(CPLMalloc(sizeof(int))); (*ppanMatchConfidence)[0] = 100; } for(size_t j=0; j<nSameCount; j++) { if( iExtraMatch != j ) delete apoSameSRS[j]; } return pahRet; } if( nSameCount == 0 && anMatchingSRSNameIndices.size() == 1 ) { const OGRSpatialReference* poOtherSRS = (*papoSRSCache)[anMatchingSRSNameIndices[0]]; OGRSpatialReferenceH* pahRet = static_cast<OGRSpatialReferenceH*>( CPLCalloc(sizeof(OGRSpatialReferenceH), 2)); pahRet[0] = reinterpret_cast<OGRSpatialReferenceH>(poOtherSRS->Clone()); if( pnEntries ) *pnEntries = 1; if( ppanMatchConfidence ) { *ppanMatchConfidence = static_cast<int*>(CPLMalloc(sizeof(int))); (*ppanMatchConfidence)[0] = 50; } return pahRet; } if( nSameCount == 0 ) return nullptr; if( pnEntries ) *pnEntries = static_cast<int>(nSameCount); OGRSpatialReferenceH* pahRet = static_cast<OGRSpatialReferenceH*>( CPLCalloc(sizeof(OGRSpatialReferenceH), nSameCount + 1)); if( ppanMatchConfidence ) { *ppanMatchConfidence = static_cast<int*>( CPLMalloc(sizeof(int) * (nSameCount + 1))); } for(size_t i=0; i<nSameCount; i++) { pahRet[i] = reinterpret_cast<OGRSpatialReferenceH>(apoSameSRS[i]); if( ppanMatchConfidence ) (*ppanMatchConfidence)[i] = 90; // Arbitrary... } pahRet[ nSameCount ] = nullptr; return pahRet; }